Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.DataFlowAnalysis.FlowState;
import com.google.javascript.jscomp.LiveVariablesAnalysis.LiveVariableLattice;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Removes local variable assignments that are useless based on information from
* {@link LiveVariablesAnalysis}. If there is an assignment to variable
* {@code x} and {@code x} is dead after this assignment, we know that the
* current content of {@code x} will not be read and this assignment is useless.
*
*/
class DeadAssignmentsElimination extends AbstractPostOrderCallback implements
CompilerPass, ScopedCallback {
private final AbstractCompiler compiler;
private LiveVariablesAnalysis liveness;
// Matches all assignment operators and increment/decrement operators.
// Does *not* match VAR initialization, since RemoveUnusedVariables
// will already remove variables that are initialized but unused.
private static final Predicate<Node> matchRemovableAssigns =
new Predicate<Node>() {
@Override
public boolean apply(Node n) {
return (NodeUtil.isAssignmentOp(n) &&
n.getFirstChild().getType() == Token.NAME) ||
n.getType() == Token.INC || n.getType() == Token.DEC;
}
};
public DeadAssignmentsElimination(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(externs);
Preconditions.checkNotNull(root);
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void enterScope(NodeTraversal t) {
Scope scope = t.getScope();
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> // Global scope _SHOULD_ work, however, liveness won't finish without
// -Xmx1024 in closure. We might have to look at coding conventions for
// exported variables as well.
if (scope.isGlobal()) {
return;
}
// We are not going to do any dead assignment elimination in when there is
// at least one inner function because in most browsers, when there is a
// closure, ALL the variables are saved (escaped).
Node fnBlock = t.getScopeRoot().getLastChild();
if (NodeUtil.containsFunction(fnBlock)) {
return;
}
// We don't do any dead assignment elimination if there are no assigns
// to eliminate. :)
if (!NodeUtil.has(fnBlock, matchRemovableAssigns,
Predicates.<Node>alwaysTrue())) {
return;
}
// Computes liveness information first.
ControlFlowGraph<Node> cfg = t.getControlFlowGraph();
liveness = new LiveVariablesAnalysis(cfg, scope, compiler);
liveness.analyze();
tryRemoveDeadAssignments(t, cfg);
}
@Override
public void exitScope(NodeTraversal t) {
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
}
/**
* Try to remove useless assignments from a control flow graph that has been
* annotated with liveness information.
*
* @param t The node traversal.
* @param cfg The control flow graph of the program annotated with liveness
* information.
*/
private void tryRemoveDeadAssignments(NodeTraversal t,
ControlFlowGraph<Node> cfg) {
Iterable<DiGraphNode<Node, Branch>> nodes = cfg.getDirectedGraphNodes();
for (DiGraphNode<Node, Branch> cfgNode : nodes) {
FlowState<LiveVariableLattice> state =
cfgNode.getAnnotation();
Node n = cfgNode.getValue();
if (n == null) {
continue;
}
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
case Token.DO:
tryRemoveAssignment(t, NodeUtil.getConditionExpression(n), state);
continue;
case Token.FOR:
if (!NodeUtil.isForIn(n)) {
tryRemoveAssignment(
t, NodeUtil.getConditionExpression(n), state);
}
continue;
case Token.SWITCH:
case Token.CASE:
case Token.RETURN:
if (n.hasChildren()) {
tryRemoveAssignment(t, n.getFirstChild(), state);
}
continue;
// TODO(user): case Token.VAR: Remove var a=1;a=2;.....
}
tryRemoveAssignment(t, n, state);
}
}
private void tryRemoveAssignment(NodeTraversal t, Node n,
FlowState<LiveVariableLattice> state) {
tryRemoveAssignment(t, n, n, state);
}
/**
*
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Determines if any local variables are dead after the instruction {@code n}
* and are assigned within the subtree of {@code n}. Removes those assignments
* if there are any.
*
* @param n Target instruction.
* @param exprRoot The CFG node where the liveness information in state is
* still correct.
* @param state The liveness information at {@code n}.
*/
private void tryRemoveAssignment(NodeTraversal t, Node n, Node exprRoot,
FlowState<LiveVariableLattice> state) {
Node parent = n.getParent();
if (NodeUtil.isAssignmentOp(n) ||
n.getType() == Token.INC || n.getType() == Token.DEC) {
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
// Recurse first. Example: dead_x = dead_y = 1; We try to clean up dead_y
// first.
if (rhs != null) {
tryRemoveAssignment(t, rhs, exprRoot, state);
rhs = lhs.getNext();
}
Scope scope = t.getScope();
if (!NodeUtil.isName(lhs)) {
return; // Not a local variable assignment.
}
String name = lhs.getString();
if (!scope.isDeclared(name, false)) {
return;
}
Var var = scope.getVar(name);
if (liveness.getEscapedLocals().contains(var)) {
return; // Local variable that might be escaped due to closures.
}
// If we have an identity assignment such as a=a, always remove it
// regardless of what the liveness results because it
// does not change the result afterward.
if (rhs != null &&
NodeUtil.isName(rhs) &&
rhs.getString().equals(var.name) &&
NodeUtil.isAssign(n)) {
n.removeChild(rhs);
n.getParent().replaceChild(n, rhs);
compiler.reportCodeChange();
return;
}
if (state.getOut().isLive(var)) {
return; // Variable not dead.
}
if (state.getIn().isLive(var) &&
isVariableStillLiveWithinExpression(n, exprRoot, var.name)) {
// The variable is killed here but it is also live before it.
// This is possible if we have say:
// if (X = a && a = C) {..} ; .......; a = S;
// In this case we are safe to remove "a = C" because it is dead.
// However if we have:
// if (a = C && X = a) {..} ; .......; a = S;
// removing "a = C" is NOT correct, although the live set at the node
// is exactly the same.
// TODO(user): We need more fine grain CFA or we need to keep track
// of GEN sets when we recurse here.
return;
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
if (NodeUtil.isAssign(n)) {
n.removeChild(rhs);
n.getParent().replaceChild(n, rhs);
} else if (NodeUtil.isAssignmentOp(n)) {
n.removeChild(rhs);
n.removeChild(lhs);
Node op = new Node(NodeUtil.getOpFromAssignmentOp(n), lhs, rhs);
parent.replaceChild(n, op);
} else if (n.getType() == Token.INC || n.getType() == Token.DEC) {
if (NodeUtil.isExpressionNode(parent)) {
parent.replaceChild(n,
new Node(Token.VOID, Node.newNumber(0).copyInformationFrom(n)));
} else if(n.getType() == Token.COMMA && n != parent.getLastChild()) {
parent.removeChild(n);
} else if (parent.getType() == Token.FOR && !NodeUtil.isForIn(parent) &&
NodeUtil.getConditionExpression(parent) != n) {
parent.replaceChild(n, new Node(Token.EMPTY));
} else {
// Cannot replace x = a++ with x = a because that's not valid
// when a is not a number.
return;
}
} else {
// Not reachable.
Preconditions.checkState(false, "Unknown statement");
}
compiler.reportCodeChange();
return;
} else {
for (Node c = n.getFirstChild(); c != null;) {
Node next = c.getNext();
if (!ControlFlowGraph.isEnteringNewCfgNode(c)) {
tryRemoveAssignment(t, c, exprRoot, state);
}
c = next;
}
return;
}
}
/**
* Given a variable, node n in the tree and a sub-tree denoted by exprRoot as
* the root, this function returns true if there exists a read of that
* variable before a write to that variable that is on the right side of n.
*
* For example, suppose the node is x = 1:
*
* y = 1, x = 1; // false, there is no reads at all.
* y = 1, x = 1, print(x) // true, there is a read right of n.
* y = 1, x = 1, x = 2, print(x) // false, there is a read right of n but
* // it is after a write.
*
* @param n The current node we should look at.
* @param exprRoot The node
*/
private boolean isVariableStillLiveWithinExpression(
Node n, Node exprRoot, String variable) {
while (n != exprRoot) {
for(Node sibling = n.getNext(); sibling != null;
sibling = sibling.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(sibling)) {
VariableLiveness state = isVariableReadBeforeKill(sibling, variable);
// If we see a READ
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.sourceBuffer = new char[512];
this.sourceEnd = 0;
} else {
if (sourceString == null) Kit.codeBug();
this.sourceString = sourceString;
this.sourceEnd = sourceString.length();
}
this.sourceCursor = 0;
}
/* This function uses the cached op, string and number fields in
* TokenStream; if getToken has been called since the passed token
* was scanned, the op or string printed may be incorrect.
*/
String tokenToString(int token)
{
if (Token.printTrees) {
String name = Token.name(token);
switch (token) {
case Token.STRING:
case Token.REGEXP:
case Token.NAME:
return name + " `" + this.string + "'";
case Token.NUMBER:
return "NUMBER " + this.number;
}
return name;
}
return "";
}
public static boolean isKeyword(String s)
{
return Token.EOF != stringToKeyword(s);
}
private static int stringToKeyword(String name)
{
// #string_id_map#
// The following assumes that Token.EOF == 0
final int
Id_break = Token.BREAK,
Id_case = Token.CASE,
Id_continue = Token.CONTINUE,
Id_default = Token.DEFAULT,
Id_delete = Token.DELPROP,
Id_do = Token.DO,
Id_else = Token.ELSE,
Id_export = Token.EXPORT,
Id_false = Token.FALSE,
Id_for = Token.FOR,
Id_function = Token.FUNCTION,
Id_if = Token.IF,
Id_in = Token.IN,
Id_new = Token.NEW,
Id_null = Token.NULL,
Id_return = Token.RETURN,
Id_switch = Token.SWITCH,
Id_this = Token.THIS,
Id_true = Token.TRUE,
Id_typeof = Token.TYPEOF,
Id_var = Token.VAR,
Id_void = Token.VOID,
Id_while = Token.WHILE,
Id_with = Token.WITH,
// the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c
Id_abstract = Token.RESERVED,
Id_boolean = Token.RESERVED,
Id_byte = Token.RESERVED,
Id_catch = Token.CATCH,
Id_char = Token.RESERVED,
Id_class = Token.RESERVED,
Id_const = Token.CONST,
Id_debugger = Token.DEBUGGER,
Id_double = Token.RESERVED,
Id_enum = Token.RESERVED,
Id_extends = Token.RESERVED,
Id_final = Token.RESERVED,
Id_finally = Token.FINALLY,
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
Id_float = Token.RESERVED,
Id_goto = Token.RESERVED,
Id_implements = Token.RESERVED,
Id_import = Token.IMPORT,
Id_instanceof = Token.INSTANCEOF,
Id_int = Token.RESERVED,
Id_interface = Token.RESERVED,
Id_long = Token.RESERVED,
Id_native = Token.RESERVED,
Id_package = Token.RESERVED,
Id_private = Token.RESERVED,
Id_protected = Token.RESERVED,
Id_public = Token.RESERVED,
Id_short = Token.RESERVED,
Id_static = Token.RESERVED,
Id_super = Token.RESERVED,
Id_synchronized = Token.RESERVED,
Id_throw = Token.THROW,
Id_throws = Token.RESERVED,
Id_transient = Token.RESERVED,
Id_try = Token.TRY,
Id_volatile = Token.RESERVED;
int id;
String s = name;
// #generated# Last update: 2001-06-01 17:45:01 CEST
L0: { id = 0; String X = null; int c;
L: switch (s.length()) {
case 2: c=s.charAt(1);
if (c=='f') { if (s.charAt(0)=='i') {id=Id_if; break L0;} }
else if (c=='n') { if (s.charAt(0)=='i') {id=Id_in; break L0;} }
else if (c=='o') { if (s.charAt(0)=='d') {id=Id_do; break L0;} }
break L;
case 3: switch (s.charAt(0)) {
case 'f': if (s.charAt(2)=='r' && s.charAt(1)=='o') {id=Id_for; break L0;} break L;
case 'i': if (s.charAt(2)=='t' && s.charAt(1)=='n') {id=Id_int; break L0;} break L;
case 'n': if (s.charAt(2)=='w' && s.charAt(1)=='e') {id=Id_new; break L0;} break L;
case 't': if (s.charAt(2)=='y' && s.charAt(1)=='r') {id=Id_try; break L0;} break L;
case 'v': if (s.charAt(2)=='r' && s.charAt(1)=='a') {id=Id_var; break L0;} break L;
} break L;
case 4: switch (s.charAt(0)) {
case 'b': X="byte";id=Id_byte; break L;
case 'c': c=s.charAt(3);
if (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>native; break L;
case 'e': c=s.charAt(0);
if (c=='d') { X="delete";id=Id_delete; }
else if (c=='r') { X="return";id=Id_return; }
break L;
case 'h': X="throws";id=Id_throws; break L;
case 'm': X="import";id=Id_import; break L;
case 'o': X="double";id=Id_double; break L;
case 't': X="static";id=Id_static; break L;
case 'u': X="public";id=Id_public; break L;
case 'w': X="switch";id=Id_switch; break L;
case 'x': X="export";id=Id_export; break L;
case 'y': X="typeof";id=Id_typeof; break L;
} break L;
case 7: switch (s.charAt(1)) {
case 'a': X="package";id=Id_package; break L;
case 'e': X="default";id=Id_default; break L;
case 'i': X="finally";id=Id_finally; break L;
case 'o': X="boolean";id=Id_boolean; break L;
case 'r': X="private";id=Id_private; break L;
case 'x': X="extends";id=Id_extends; break L;
} break L;
case 8: switch (s.charAt(0)) {
case 'a': X="abstract";id=Id_abstract; break L;
case 'c': X="continue";id=Id_continue; break L;
case 'd': X="debugger";id=Id_debugger; break L;
case 'f': X="function";id=Id_function; break L;
case 'v': X="volatile";id=Id_volatile; break L;
} break L;
case 9: c=s.charAt(0);
if (c=='i') { X="interface";id=Id_interface; }
else if (c=='p') { X="protected";id=Id_protected; }
else if (c=='t') { X="transient";id=Id_transient; }
break L;
case 10: c=s.charAt(1);
if (c=='m') { X="implements";id=Id_implements; }
else if (c=='n') { X="instanceof";id=Id_instanceof; }
break L;
case 12: X="synchronized";id=Id_synchronized; break L;
}
if (X!=null && X!=s && !X.equals(s)) id = 0;
}
// #/generated#
// #/string_id_map#
if (id == 0) { return Token.EOF; }
return id & 0xff;
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> public static boolean isJSIdentifier(String s) {
int length = s.length();
if (length == 0 || !Character.isJavaIdentifierStart(s.charAt(0)))
return false;
for (int i=1; i<length; i++) {
char c = s.charAt(i);
if (!Character.isJavaIdentifierPart(c)) {
if (c == '\\') {
if (! ((i + 5) < length)
&& (s.charAt(i + 1) == 'u')
&& 0 <= Kit.xDigitToInt(s.charAt(i + 2), 0)
&& 0 <= Kit.xDigitToInt(s.charAt(i + 3), 0)
&& 0 <= Kit.xDigitToInt(s.charAt(i + 4), 0)
&& 0 <= Kit.xDigitToInt(s.charAt(i + 5), 0)) {
return true;
}
}
return false;
}
}
return true;
}
protected final int getLineno() { return lineno; }
protected final int getCharno() { return charno; }
final String getString() { return string; }
final double getNumber() { return number; }
final boolean eof() { return hitEOF; }
public final int getToken() throws IOException
{
tokenno++;
// Check for pushed-back token
if (this.pushbackToken != Token.EOF) {
int result = this.pushbackToken;
this.pushbackToken = Token.EOF;
return result;
}
int c;
retry:
for (;;) {
// Eat whitespace, possibly sensitive to newlines.
for (;;) {
charno = -1;
c = getChar();
if (c == EOF_CHAR) {
return Token.EOF;
} else if (c == '\n') {
dirtyLine = false;
return Token.EOL;
} else if (!isJSSpace(c)) {
if (c != '-') {
dirtyLine = true;
}
break;
}
}
if (c == '@') return Token.XMLATTR;
// identifier/keyword/instanceof?
// watch out for starting with a <backslash>
boolean identifierStart;
boolean isUnicodeEscapeStart = false;
if (c == '\\') {
c = getChar();
if (c == 'u') {
identifierStart = true;
isUnicodeEscapeStart = true;
stringBufferTop = 0;
} else {
identifierStart = false;
ungetChar(c);
c = '\\';
}
} else {
identifierStart = Character.isJavaIdentifierStart((char)c);
if (identifierStart) {
stringBufferTop = 0;
addToString(c);
}
}
if (identifierStart) {
boolean containsEscape = isUnicodeEscapeStart;
for (;;) {
if (isUnicodeEscapeStart) {
// strictly speaking we
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> should probably push-back
// all the bad characters if the <backslash>uXXXX
// sequence is malformed. But since there isn't a
// correct context(is there?) for a bad Unicode
// escape sequence in an identifier, we can report
// an error here.
int escapeVal = 0;
for (int i = 0; i != 4; ++i) {
c = getChar();
escapeVal = Kit.xDigitToInt(c, escapeVal);
// Next check takes care about c < 0 and bad escape
if (escapeVal < 0) { break; }
}
if (escapeVal < 0) {
parser.addError("msg.invalid.escape");
return Token.ERROR;
}
addToString(escapeVal);
isUnicodeEscapeStart = false;
} else {
c = getChar();
if (c == '\\') {
c = getChar();
if (c == 'u') {
isUnicodeEscapeStart = true;
containsEscape = true;
} else {
parser.addError("msg.illegal.character");
return Token.ERROR;
}
} else {
if (c == EOF_CHAR
|| !Character.isJavaIdentifierPart((char)c))
{
break;
}
addToString(c);
}
}
}
ungetChar(c);
String str = getStringFromBuffer();
if (!containsEscape) {
// OPT we shouldn't have to make a string (object!) to
// check if it's a keyword.
// Return the corresponding token if it's a keyword
int result = stringToKeyword(str);
if (result != Token.EOF) {
if (result != Token.RESERVED) {
return result;
} else if (!parser.compilerEnv.
isReservedKeywordAsIdentifier())
{
return result;
} else {
// If implementation permits to use future reserved
// keywords in violation with the EcmaScript
// standard, treat it as name but issue warning
parser.addWarning("msg.reserved.keyword", str);
}
}
}
this.string = (String)allStrings.intern(str);
return Token.NAME;
}
// is it a number?
if (isDigit(c) || (c == '.' && isDigit(peekChar()))) {
stringBufferTop = 0;
int base = 10;
if (c == '0') {
c = getChar();
if (c == 'x' || c == 'X') {
base = 16;
c = getChar();
} else if (isDigit(c)) {
base = 8;
} else {
addToString('0');
}
}
if (base == 16) {
while (0 <= Kit.xDigitToInt(c, 0)) {
addToString(c);
c = getChar();
}
} else {
while ('0' <= c && c <= '9') {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> /*
* We permit 08 and 09 as decimal numbers, which
* makes our behavior a superset of the ECMA
* numeric grammar. We might not always be so
* permissive, so we warn about it.
*/
if (base == 8 && c >= '8') {
parser.addWarning("msg.bad.octal.literal",
c == '8' ? "8" : "9");
base = 10;
}
addToString(c);
c = getChar();
}
}
boolean isInteger = true;
if (base == 10 && (c == '.' || c == 'e' || c == 'E')) {
isInteger = false;
if (c == '.') {
do {
addToString(c);
c = getChar();
} while (isDigit(c));
}
if (c == 'e' || c == 'E') {
addToString(c);
c = getChar();
if (c == '+' || c == '-') {
addToString(c);
c = getChar();
}
if (!isDigit(c)) {
parser.addError("msg.missing.exponent");
return Token.ERROR;
}
do {
addToString(c);
c = getChar();
} while (isDigit(c));
}
}
ungetChar(c);
String numString = getStringFromBuffer();
double dval;
if (base == 10 && !isInteger) {
try {
// Use Java conversion to number from string...
dval = Double.valueOf(numString).doubleValue();
}
catch (NumberFormatException ex) {
parser.addError("msg.caught.nfe");
return Token.ERROR;
}
} else {
dval = ScriptRuntime.stringToNumber(numString, 0, base);
}
this.number = dval;
return Token.NUMBER;
}
// is it a string?
if (c == '"' || c == '\'') {
// We attempt to accumulate a string the fast way, by
// building it directly out of the reader. But if there
// are any escaped characters in the string, we revert to
// building it out of a StringBuffer.
int quoteChar = c;
stringBufferTop = 0;
c = getChar();
strLoop: while (c != quoteChar) {
if (c == '\n' || c == EOF_CHAR) {
ungetChar(c);
parser.addError("msg.unterminated.string.lit");
return Token.ERROR;
}
if (c == '\\') {
// We've hit an escaped character
int escapeVal;
c = getChar();
switch (c) {
case 'b': c = '\b'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> 't': c = '\t'; break;
// \v a late addition to the ECMA spec,
// it is not in Java, so use 0xb
case 'v': c = 0xb; break;
case 'u':
// Get 4 hex digits; if the u escape is not
// followed by 4 hex digits, use 'u' + the
// literal character sequence that follows.
int escapeStart = stringBufferTop;
addToString('u');
escapeVal = 0;
for (int i = 0; i != 4; ++i) {
c = getChar();
escapeVal = Kit.xDigitToInt(c, escapeVal);
if (escapeVal < 0) {
continue strLoop;
}
addToString(c);
}
// prepare for replace of stored 'u' sequence
// by escape value
stringBufferTop = escapeStart;
c = escapeVal;
break;
case 'x':
// Get 2 hex digits, defaulting to 'x'+literal
// sequence, as above.
c = getChar();
escapeVal = Kit.xDigitToInt(c, 0);
if (escapeVal < 0) {
addToString('x');
continue strLoop;
} else {
int c1 = c;
c = getChar();
escapeVal = Kit.xDigitToInt(c, escapeVal);
if (escapeVal < 0) {
addToString('x');
addToString(c1);
continue strLoop;
} else {
// got 2 hex digits
c = escapeVal;
}
}
break;
case '\n':
// Remove line terminator after escape to follow
// SpiderMonkey and C/C++
c = getChar();
continue strLoop;
default:
if ('0' <= c && c < '8') {
int val = c - '0';
c = getChar();
if ('0' <= c && c < '8') {
val = 8 * val + c - '0';
c = getChar();
if ('0' <= c && c < '8' && val <= 037) {
// c is 3rd char of octal sequence only
// if the resulting val <= 0377
val = 8 * val + c - '0';
c = getChar();
}
}
ungetChar(c);
c = val;
}
}
}
addToString(c);
c = getChar();
}
String str = getStringFromBuffer();
this.string = (String)allStrings.intern(str);
return Token.STRING;
}
switch (c) {
case ';': return Token.SEMI;
case '[': return Token.LB;
case ']': return Token.RB;
case '{': return Token.LC;
case '}': return Token.RC;
case '(': return Token.LP;
case ')': return Token.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>RP;
case ',': return Token.COMMA;
case '?': return Token.HOOK;
case ':':
if (matchChar(':')) {
return Token.COLONCOLON;
} else {
return Token.COLON;
}
case '.':
if (matchChar('.')) {
return Token.DOTDOT;
} else if (matchChar('(')) {
return Token.DOTQUERY;
} else {
return Token.DOT;
}
case '|':
if (matchChar('|')) {
return Token.OR;
} else if (matchChar('=')) {
return Token.ASSIGN_BITOR;
} else {
return Token.BITOR;
}
case '^':
if (matchChar('=')) {
return Token.ASSIGN_BITXOR;
} else {
return Token.BITXOR;
}
case '&':
if (matchChar('&')) {
return Token.AND;
} else if (matchChar('=')) {
return Token.ASSIGN_BITAND;
} else {
return Token.BITAND;
}
case '=':
if (matchChar('=')) {
if (matchChar('='))
return Token.SHEQ;
else
return Token.EQ;
} else {
return Token.ASSIGN;
}
case '!':
if (matchChar('=')) {
if (matchChar('='))
return Token.SHNE;
else
return Token.NE;
} else {
return Token.NOT;
}
case '<':
/* NB:treat HTML begin-comment as comment-till-eol */
if (matchChar('!')) {
if (matchChar('-')) {
if (matchChar('-')) {
skipLine();
continue retry;
}
ungetChar('-');
}
ungetChar('!');
}
if (matchChar('<')) {
if (matchChar('=')) {
return Token.ASSIGN_LSH;
} else {
return Token.LSH;
}
} else {
if (matchChar('=')) {
return Token.LE;
} else {
return Token.LT;
}
}
case '>':
if (matchChar('>')) {
if (matchChar('>')) {
if (matchChar('=')) {
return Token.ASSIGN_URSH;
} else {
return Token.URSH;
}
} else {
if (matchChar('=')) {
return Token.ASSIGN_RSH;
} else {
return Token.RSH;
}
}
} else {
if (matchChar('=')) {
return Token.GE;
} else {
return Token.GT;
}
}
case '*':
if (matchChar('=')) {
return Token.ASSIGN_MUL;
} else {
return Token.MUL;
}
case '/':
// is it a // comment?
if (matchChar('/')) {
skipLine();
continue retry;
}
if (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>matchChar('*')) {
while ((c = getChar()) != EOF_CHAR &&
!(c == '*' && matchChar('/'))) {
// empty loop body
}
if (c == EOF_CHAR) {
parser.addError("msg.unterminated.comment");
return Token.ERROR;
}
continue retry;
}
if (matchChar('=')) {
return Token.ASSIGN_DIV;
} else {
return Token.DIV;
}
case '%':
if (matchChar('=')) {
return Token.ASSIGN_MOD;
} else {
return Token.MOD;
}
case '~':
return Token.BITNOT;
case '+':
if (matchChar('=')) {
return Token.ASSIGN_ADD;
} else if (matchChar('+')) {
return Token.INC;
} else {
return Token.ADD;
}
case '-':
if (matchChar('=')) {
c = Token.ASSIGN_SUB;
} else if (matchChar('-')) {
if (!dirtyLine) {
// treat HTML end-comment after possible whitespace
// after line start as comment-utill-eol
if (matchChar('>')) {
skipLine();
continue retry;
}
}
c = Token.DEC;
} else {
c = Token.SUB;
}
dirtyLine = true;
return c;
default:
parser.addError("msg.illegal.character");
return Token.ERROR;
}
}
}
/**
* Tokenizes JSDoc comments.
*/
@SuppressWarnings("fallthrough")
final int getJSDocToken() throws IOException {
int c;
stringBufferTop = 0;
for (;;) {
// eat white spaces
for (;;) {
charno = -1;
c = getChar();
if (c == EOF_CHAR) {
return Token.EOF;
} else if (c == '\n') {
return Token.EOL;
} else if (!isJSSpace(c)) {
break;
}
}
switch (c) {
// annotation, e.g. @type or @constructor
case '@':
do {
c = getChar();
if (isAlpha(c)) {
addToString(c);
} else {
ungetChar(c);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return Token.ANNOTATION;
}
} while (true);
case '*':
if (matchChar('/')) {
return Token.EOC;
} else {
return Token.STAR;
}
case ',':
return Token.COMMA;
case '>':
return Token.GT;
case '(':
return Token.LP;
case ')':
return Token.RP;
case '{':
return Token.LC;
case '}':
return Token.RC;
case '[':
return Token.LB;
case ']':
return Token.RB;
case '?':
return Token.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>QMARK;
case '!':
return Token.BANG;
case ':':
return Token.COLON;
case '=':
return Token.EQUALS;
case '|':
matchChar('|');
return Token.PIPE;
case '.':
c = getChar();
if (c == '<') {
return Token.LT;
} else {
if (c == '.') {
c = getChar();
if (c == '.') {
return Token.ELLIPSIS;
} else {
addToString('.');
}
}
// we may backtrack across line boundary
ungetBuffer[ungetCursor++] = c;
c = '.';
}
// fall through
default: {
// recognize a jsdoc string but discard last . if it is followed by
// a non-jsdoc comment char, e.g. Array.<
int c1 = c;
addToString(c);
int c2 = getChar();
if (!isJSDocString(c2)) {
ungetChar(c2);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return Token.STRING;
} else {
do {
c1 = c2;
c2 = getChar();
if (c1 == '.' && c2 == '<') {
ungetChar(c2);
ungetChar(c1);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return Token.STRING;
} else {
if (isJSDocString(c2)) {
addToString(c1);
} else {
ungetChar(c2);
addToString(c1);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return Token.STRING;
}
}
} while (true);
}
}
}
}
}
/**
* Gets the remaining JSDoc line without the {@link Token#EOL},
* {@link Token#EOF} or {@link Token#EOC}.
*/
@SuppressWarnings("fallthrough")
String getRemainingJSDocLine() throws IOException {
int c;
for (;;) {
c = getChar();
switch (c) {
case '*':
if (peekChar() != '/') {
addToString(c);
break;
}
// fall through
case EOF_CHAR:
case '\n':
ungetChar(c);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return this.string;
default:
addToString(c);
break;
}
}
}
private boolean isJSDocString(int c) {
switch (c) {
case '@':
case '*':
case ',':
case '>':
case ':':
case '(':
case ')':
case '{':
case '}':
case '[':
case ']':
case '?':
case '!':
case '|':
case '=':
case EOF_CHAR:
case '\n':
return false
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
default:
return !isJSSpace(c);
}
}
private static boolean isAlpha(int c)
{
// Use 'Z' < 'a'
if (c <= 'Z') {
return 'A' <= c;
} else {
return 'a' <= c && c <= 'z';
}
}
static boolean isDigit(int c)
{
return '0' <= c && c <= '9';
}
/**
* Tests whether the character is a valid JavaScript white space character
* as defined in ECMAScript 3rd edition.
*
* Note: jsscan.c uses C isspace() (which allows
* \v, I think.) note that code in getChar() implicitly accepts
* '\r' == \u000D as well.
*/
static boolean isJSSpace(int c)
{
if (c <= 127) {
return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB;
} else {
return c == 0xA0
|| Character.getType((char)c) == Character.SPACE_SEPARATOR;
}
}
private static boolean isJSFormatChar(int c)
{
return c > 127 && Character.getType((char)c) == Character.FORMAT;
}
/**
* Gets the accumulated {@link JSDocInfo} and resets it.
* Obsolete
*/
JSDocInfo getAndResetJSDocInfo() {
return null;
}
/**
* Returns any {@link JSDocInfo} with a fileoverview tag that showed up.
* Obsolete
*/
JSDocInfo getFileOverviewJSDocInfo() {
return null;
}
/**
* Returns whether any {@link JSDocInfo} was accumulated.
* Obsolete
*/
boolean isPopulated() {
return false;
}
/**
* Parser calls the method when it gets / or /= in literal context.
*/
void readRegExp(int startToken)
throws IOException
{
stringBufferTop = 0;
if (startToken == Token.ASSIGN_DIV) {
// Miss-scanned /=
addToString('=');
} else {
if (startToken != Token.DIV) Kit.codeBug();
}
boolean inCharSet = false; // true if inside a '['..']' pair
int c;
while ((c = getChar()) != '/' || inCharSet) {
if (c == '\n' || c == EOF_CHAR) {
ungetChar(c);
throw parser.reportError("msg.unterminated.re.lit");
}
if (c == '\\') {
addToString(c);
c = getChar();
} else if (c == '[') {
inCharSet = true;
} else if (c == ']') {
inCharSet = false;
}
addToString
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(c);
}
int reEnd = stringBufferTop;
while (true) {
if (matchChar('g'))
addToString('g');
else if (matchChar('i'))
addToString('i');
else if (matchChar('m'))
addToString('m');
else
break;
}
if (isAlpha(peekChar())) {
throw parser.reportError("msg.invalid.re.flag");
}
this.string = new String(stringBuffer, 0, reEnd);
this.regExpFlags = new String(stringBuffer, reEnd,
stringBufferTop - reEnd);
}
boolean isXMLAttribute()
{
return xmlIsAttribute;
}
int getFirstXMLToken() throws IOException
{
xmlOpenTagsCount = 0;
xmlIsAttribute = false;
xmlIsTagContent = false;
ungetChar('<');
return getNextXMLToken();
}
int getNextXMLToken() throws IOException
{
stringBufferTop = 0; // remember the XML
for (int c = getChar(); c != EOF_CHAR; c = getChar()) {
if (xmlIsTagContent) {
switch (c) {
case '>':
addToString(c);
xmlIsTagContent = false;
xmlIsAttribute = false;
break;
case '/':
addToString(c);
if (peekChar() == '>') {
c = getChar();
addToString(c);
xmlIsTagContent = false;
xmlOpenTagsCount--;
}
break;
case '{':
ungetChar(c);
this.string = getStringFromBuffer();
return Token.XML;
case '\'':
case '"':
addToString(c);
if (!readQuotedString(c)) return Token.ERROR;
break;
case '=':
addToString(c);
xmlIsAttribute = true;
break;
case ' ':
case '\t':
case '\r':
case '\n':
addToString(c);
break;
default:
addToString(c);
xmlIsAttribute = false;
break;
}
if (!xmlIsTagContent && xmlOpenTagsCount == 0) {
this.string = getStringFromBuffer();
return Token.XMLEND;
}
} else {
switch (c) {
case '<':
addToString(c);
c = peekChar();
switch (c) {
case '!':
c = getChar(); // Skip !
addToString(c);
c = peekChar();
switch (c) {
case '-':
c = getChar(); // Skip -
addToString(c);
c = getChar();
if (c == '-') {
addToString(c);
if(!readXmlComment()) return Token.ERROR;
} else {
// throw away the string in progress
stringBufferTop = 0;
this.string = null;
parser.addError("msg.XML.bad.form");
return Token.ERROR;
}
break;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
case '[':
c = getChar(); // Skip [
addToString(c);
if (getChar() == 'C' &&
getChar() == 'D' &&
getChar() == 'A' &&
getChar() == 'T' &&
getChar() == 'A' &&
getChar() == '[')
{
addToString('C');
addToString('D');
addToString('A');
addToString('T');
addToString('A');
addToString('[');
if (!readCDATA()) return Token.ERROR;
} else {
// throw away the string in progress
stringBufferTop = 0;
this.string = null;
parser.addError("msg.XML.bad.form");
return Token.ERROR;
}
break;
default:
if(!readEntity()) return Token.ERROR;
break;
}
break;
case '?':
c = getChar(); // Skip ?
addToString(c);
if (!readPI()) return Token.ERROR;
break;
case '/':
// End tag
c = getChar(); // Skip /
addToString(c);
if (xmlOpenTagsCount == 0) {
// throw away the string in progress
stringBufferTop = 0;
this.string = null;
parser.addError("msg.XML.bad.form");
return Token.ERROR;
}
xmlIsTagContent = true;
xmlOpenTagsCount--;
break;
default:
// Start tag
xmlIsTagContent = true;
xmlOpenTagsCount++;
break;
}
break;
case '{':
ungetChar(c);
this.string = getStringFromBuffer();
return Token.XML;
default:
addToString(c);
break;
}
}
}
stringBufferTop = 0; // throw away the string in progress
this.string = null;
parser.addError("msg.XML.bad.form");
return Token.ERROR;
}
/**
*
*/
private boolean readQuotedString(int quote) throws IOException
{
for (int c = getChar(); c != EOF_CHAR; c = getChar()) {
addToString(c);
if (c == quote) return true;
}
stringBufferTop = 0; // throw away the string in progress
this.string = null;
parser.addError("msg.XML.bad.form");
return false;
}
/**
*
*/
private boolean readXmlComment() throws IOException
{
for (int c = getChar(); c != EOF_CHAR;) {
addToString(c);
if (c == '-' && peekChar() == '-') {
c = getChar();
addToString(c);
if (peekChar() == '>') {
c = getChar(); // Skip >
addToString(c);
return true;
} else {
continue;
}
}
c = getChar();
}
stringBufferTop = 0; // throw away the string
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.get(v);
// NOTE(nicksantos): Don't handle variables that are never used.
// The tests are much easier to write if you don't, and there's
// another pass that handles unused variables much more elegantly.
if (referenceInfo != null && referenceInfo.references.size() >= 2 &&
referenceInfo.isWellDefined() &&
referenceInfo.isAssignedOnceInLifetime()) {
Reference init = referenceInfo.getInitializingReference();
Node value = init.getAssignedValue();
if (value != null && value.getType() == Token.NAME) {
aliasCandidates.put(value, new AliasCandidate(v, referenceInfo));
}
}
}
}
}
/**
* For all variables in this scope, see if they are only used once.
* If it looks safe to do so, inline them.
*/
private void doInlinesForScope(NodeTraversal t,
Map<Var, ReferenceCollection> referenceMap) {
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
ReferenceCollection referenceInfo = referenceMap.get(v);
// referenceInfo will be null if we're in constants-only mode
// and the variable is not a constant.
if (referenceInfo == null || isVarInlineForbidden(v)) {
// Never try to inline exported variables or variables that
// were not collected or variables that have already been inlined.
continue;
} else if (isInlineableDeclaredConstant(v, referenceInfo)) {
Reference init = referenceInfo.getInitializingReferenceForConstants();
Node value = init.getAssignedValue();
inlineDeclaredConstant(v, value, referenceInfo.references);
staleVars.add(v);
} else if (mode == Mode.CONSTANTS_ONLY) {
// If we're in constants-only mode, don't run more aggressive
// inlining heuristics. See InlineConstantsTest.
continue;
} else {
inlineNonConstants(v, referenceInfo);
}
}
}
private void inlineNonConstants(
Var v, ReferenceCollection referenceInfo) {
int refCount = referenceInfo.references.size();
Reference declaration = referenceInfo.references.get(0);
Reference init = referenceInfo.getInitializingReference();
int firstRefAfterInit = (declaration == init) ? 2 : 3;
if (refCount > 1 &&
isImmutableAndWellDefinedVariable(v, referenceInfo)) {
// if the variable is referenced more than once, we can only
// inline it if it's immutable and never defined before referenced.
Node value;
if (init != null) {
value = init.getAssignedValue();
} else {
// Create a new node for variable that is never initialized.
Node srcLocation = declaration.getNameNode();
value = NodeUtil.newUndefinedNode(srcLocation);
}
Preconditions.checkNotNull(value);
inlineWellDefinedVariable(v, value, referenceInfo.references);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> staleVars.add(v);
} else if (refCount == firstRefAfterInit) {
// The variable likely only read once, try some more
// complex inlining heuristics.
Reference reference = referenceInfo.references.get(
firstRefAfterInit - 1);
if (canInline(declaration, init, reference)) {
inline(v, declaration, init, reference);
staleVars.add(v);
}
} else if (declaration != init && refCount == 2) {
if (isValidDeclaration(declaration) && isValidInitialization(init)) {
// The only reference is the initialization, remove the assignment and
// the variable declaration.
Node value = init.getAssignedValue();
Preconditions.checkNotNull(value);
inlineWellDefinedVariable(v, value, referenceInfo.references);
staleVars.add(v);
}
}
// If this variable was not inlined normally, check if we can
// inline an alias of it. (If the variable was inlined, then the
// reference data is out of sync. We're better off just waiting for
// the next pass.)
if (!staleVars.contains(v) && referenceInfo.isWellDefined() &&
referenceInfo.isAssignedOnceInLifetime()) {
List<Reference> refs = referenceInfo.references;
for (int i = 1 /* start from a read */; i < refs.size(); i++) {
Node nameNode = refs.get(i).getNameNode();
if (aliasCandidates.containsKey(nameNode)) {
AliasCandidate candidate = aliasCandidates.get(nameNode);
if (!staleVars.contains(candidate.alias)) {
Reference aliasInit;
aliasInit = candidate.refInfo.getInitializingReference();
Node value = aliasInit.getAssignedValue();
Preconditions.checkNotNull(value);
inlineWellDefinedVariable(candidate.alias,
value,
candidate.refInfo.references);
staleVars.add(candidate.alias);
}
}
}
}
}
/**
* If there are any variable references in the given node tree, blacklist
* them to prevent the pass from trying to inline the variable.
*/
private void blacklistVarReferencesInTree(Node root, Scope scope) {
for (Node c = root.getFirstChild(); c != null; c = c.getNext()) {
blacklistVarReferencesInTree(c, scope);
}
if (root.getType() == Token.NAME) {
staleVars.add(scope.getVar(root.getString()));
}
}
/**
* Whether the given variable is forbidden from being inlined.
*/
private boolean isVarInlineForbidden(Var var) {
// A variable may not be inlined if:
// 1) The variable is exported,
// 2) A reference to the variable has been inlined. We're downstream
// of the mechanism that creates variable references, so we don't
// have a good way to update the reference. Just punt on it.
// 3) Don't inline the special RENAME
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>_PROPERTY_FUNCTION_NAME
return compiler.getCodingConvention().isExported(var.name)
|| RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(var.name)
|| staleVars.contains(var);
}
/**
* Do the actual work of inlining a single declaration into a single
* reference.
*/
private void inline(Var v, Reference declaration,
Reference init, Reference reference) {
Node value = init.getAssignedValue();
Preconditions.checkState(value != null);
// Check for function declarations before the value is moved in the AST.
boolean isFunctionDeclaration = NodeUtil.isFunctionDeclaration(value);
inlineValue(v, reference, value.detachFromParent());
if (declaration != init) {
Node expressRoot = init.getGrandparent();
Preconditions.checkState(expressRoot.getType() == Token.EXPR_RESULT);
NodeUtil.removeChild(expressRoot.getParent(), expressRoot);
}
// Function declarations have already been removed.
if (!isFunctionDeclaration) {
removeDeclaration(declaration);
} else {
compiler.reportCodeChange();
}
}
/**
* Inline an immutable variable into all of its references.
*/
private void inlineWellDefinedVariable(Var v, Node value,
List<Reference> refSet) {
Reference decl = refSet.get(0);
for (int i = 1; i < refSet.size(); i++) {
inlineValue(v, refSet.get(i), value.cloneTree());
}
removeDeclaration(decl);
}
/**
* Inline a declared constant.
*/
private void inlineDeclaredConstant(Var v, Node value,
List<Reference> refSet) {
// Replace the references with the constant value
Reference decl = null;
for (Reference r : refSet) {
if (r.getNameNode() == v.getNameNode()) {
decl = r;
} else {
inlineValue(v, r, value.cloneTree());
}
}
removeDeclaration(decl);
}
/**
* Remove the given VAR declaration.
*/
private void removeDeclaration(Reference declaration) {
Node varNode = declaration.getParent();
varNode.removeChild(declaration.getNameNode());
// Remove var node if empty
if (!varNode.hasChildren()) {
Preconditions.checkState(varNode.getType() == Token.VAR);
Node grandparent = declaration.getGrandparent();
NodeUtil.removeChild(grandparent, varNode);
}
compiler.reportCodeChange();
}
/**
* Replace the given reference with the given value node.
*
* @param v The variable that's referenced.
* @param ref The reference to replace.
* @param value The node tree to replace it with. This tree should be safe
* to re-parent.
*/
private void inlineValue(Var v, Reference ref, Node value) {
if (ref.isSimpleAssignmentToName()) {
// This is the initial assignment.
ref.getGrandparent().
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>replaceChild(ref.getParent(), value);
} else {
ref.getParent().replaceChild(ref.getNameNode(), value);
}
blacklistVarReferencesInTree(value, v.scope);
compiler.reportCodeChange();
}
/**
* Determines whether the given variable is declared as a constant
* and may be inlined.
*/
private boolean isInlineableDeclaredConstant(Var var,
ReferenceCollection refInfo) {
if (!identifyConstants.apply(var)) {
return false;
}
if (!refInfo.isAssignedOnceInLifetime()) {
return false;
}
Reference init = refInfo.getInitializingReferenceForConstants();
if (init == null) {
return false;
}
Node value = init.getAssignedValue();
if (value == null) {
// This constant is either externally defined or initialized indirectly
// (e.g. in an function expression used to hide
// temporary variables), so the constant is ineligible for inlining.
return false;
}
// Is the constant's value immutable?
if (!NodeUtil.isImmutableValue(value)) {
return false;
}
// Determine if we should really inline a String or not.
return value.getType() != Token.STRING ||
isStringWorthInlining(var, refInfo.references);
}
/**
* Compute whether the given string is worth inlining.
*/
private boolean isStringWorthInlining(Var var, List<Reference> refs) {
if (!inlineAllStrings && !var.isDefine()) {
int len = var.getInitialValue().getString().length() + "''".length();
// if not inlined: var xx="value"; .. xx .. xx ..
// The 4 bytes per reference is just a heuristic:
// 2 bytes per var name plus maybe 2 bytes if we don't inline, e.g.
// in the case of "foo " + CONST + " bar"
int noInlineBytes = "var xx=;".length() + len +
4 * (refs.size() - 1);
// if inlined:
// I'm going to assume that half of the quotes will be eliminated
// thanks to constant folding, therefore I subtract 1 (2/2=1) from
// the string length.
int inlineBytes = (len - 1) * (refs.size() - 1);
// Not inlining if doing so uses more bytes, or this constant is being
// defined.
return noInlineBytes >= inlineBytes;
}
return true;
}
/**
* @return true if the provided reference and declaration can be safely
* inlined according to our criteria
*/
private boolean canInline(
Reference declaration,
Reference initialization,
Reference reference) {
if (!isValidDeclaration(declaration)
|| !isValidInitialization(initialization)
|| !isValidReference(reference)) {
return false;
}
// If the value is read more than once, skip it.
// VAR declarations and EXPR_RESULT don
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>'t need the value, but other
// ASSIGN expressions parents do.
if (declaration != initialization &&
initialization.getGrandparent().getType() != Token.EXPR_RESULT) {
return false;
}
// Be very conservative and do no cross control structures or
// scope boundaries
if (declaration.getBasicBlock() != initialization.getBasicBlock()
|| declaration.getBasicBlock() != reference.getBasicBlock()) {
return false;
}
// Do not inline into a call node. This would change
// the context in which it was being called. For example,
// var a = b.c;
// a();
// should not be inlined, because it calls a in the context of b
// rather than the context of the window.
// var a = b.c;
// f(a)
// is ok.
Node value = initialization.getAssignedValue();
Preconditions.checkState(value != null);
if (value.getType() == Token.GETPROP
&& reference.getParent().getType() == Token.CALL
&& reference.getParent().getFirstChild() == reference.getNameNode()) {
return false;
}
// Bug 2388531: Don't inline subclass definitions into class defining
// calls as this confused class removing logic.
if (value.getType() == Token.FUNCTION) {
Node callNode = reference.getParent();
if (reference.getParent().getType() == Token.CALL) {
SubclassRelationship relationship =
compiler.getCodingConvention().getClassesDefinedByCall(callNode);
if (relationship != null) {
return false;
}
}
}
return canMoveAggressively(value) ||
canMoveModerately(initialization, reference);
}
/**
* If the value is a literal, we can cross more boundaries to inline it.
*/
private boolean canMoveAggressively(Node value) {
// Function expressions and other mutable objects can move within
// the same basic block.
return NodeUtil.isLiteralValue(value, true)
|| value.getType() == Token.FUNCTION;
}
/**
* If the value of a variable is not constant, then it may read or modify
* state. Therefore it cannot be moved past anything else that may modify
* the value being read or read values that are modified.
*/
private boolean canMoveModerately(
Reference initialization,
Reference reference) {
// Check if declaration can be inlined without passing
// any side-effect causing nodes.
Iterator<Node> it;
if (initialization.getParent().getType() == Token.VAR) {
it = NodeIterators.LocalVarMotion.forVar(
initialization.getNameNode(), // NAME
initialization.getParent(), // VAR
initialization.getGrandparent()); // VAR container
} else if (initialization.getParent().getType() == Token.ASSIGN) {
Preconditions.checkState(
initialization.getGrandparent().getType() == Token.EXPR_RESULT);
it = NodeIterators.LocalVarMotion.forAssign(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
initialization.getNameNode(), // NAME
initialization.getParent(), // ASSIGN
initialization.getGrandparent(), // EXPR_RESULT
initialization.getGrandparent().getParent()); // EXPR container
} else {
throw new IllegalStateException("Unexpected initialization parent " +
initialization.getParent().toStringTree());
}
Node targetName = reference.getNameNode();
while (it.hasNext()) {
Node curNode = it.next();
if (curNode == targetName) {
return true;
}
}
return false;
}
/**
* @return true if the reference is a normal VAR or FUNCTION declaration.
*/
private boolean isValidDeclaration(Reference declaration) {
return (declaration.getParent().getType() == Token.VAR
&& declaration.getGrandparent().getType() != Token.FOR)
|| NodeUtil.isFunctionDeclaration(declaration.getParent());
}
/**
* @return Whether there is a initial value.
*/
private boolean isValidInitialization(Reference initialization) {
if (initialization == null) {
return false;
} else if (initialization.isDeclaration()) {
// The reference is a FUNCTION declaration or normal VAR declaration
// with a value.
return NodeUtil.isFunctionDeclaration(initialization.getParent())
|| initialization.getNameNode().getFirstChild() != null;
} else {
Node parent = initialization.getParent();
Preconditions.checkState(
parent.getType() == Token.ASSIGN
&& parent.getFirstChild() == initialization.getNameNode());
return true;
}
}
/**
* @return true if the reference is a candidate for inlining
*/
private boolean isValidReference(Reference reference) {
return !reference.isDeclaration() && !reference.isLvalue();
}
/**
* Determines whether the reference collection describes a variable that
* is initialized to an immutable value, never modified, and defined before
* every reference.
*/
private boolean isImmutableAndWellDefinedVariable(Var v,
ReferenceCollection refInfo) {
List<Reference> refSet = refInfo.references;
int startingReadRef = 1;
Reference refDecl = refSet.get(0);
if (!isValidDeclaration(refDecl)) {
return false;
}
boolean isNeverAssigned = refInfo.isNeverAssigned();
// For values that are never assigned, only the references need to be
// checked.
if (!isNeverAssigned) {
Reference refInit = refInfo.getInitializingReference();
if (!isValidInitialization(refInit)) {
return false;
}
if (refDecl != refInit) {
Preconditions.checkState(refInit == refSet.get(1));
startingReadRef = 2;
}
if (!refInfo.isWellDefined()) {
return false;
}
Node value = refInit.getAssignedValue();
Preconditions.checkNotNull(value);
boolean isImmutableValueWorthInlining =
NodeUtil.isImmutableValue(value) &&
(value.getType() != Token.STRING ||
isStringWorthInlining(v, refInfo.references));
boolean isInlin
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ableThisAlias =
value.getType() == Token.THIS &&
!refInfo.isEscaped();
if (!isImmutableValueWorthInlining && !isInlinableThisAlias) {
return false;
}
}
for (int i = startingReadRef; i < refSet.size(); i++) {
Reference ref = refSet.get(i);
if (!isValidReference(ref)) {
return false;
}
}
return true;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* CodePrinter prints out js code in either pretty format or compact format.
*
* @see CodeGenerator
*/
class CodePrinter {
// The number of characters after which we insert a line break in the code
static final int DEFAULT_LINE_LENGTH_THRESHOLD = 500;
// There are two separate CodeConsumers, one for pretty-printing and
// another for compact printing.
// There are two implementations because the CompactCodePrinter
// potentially has a very different implementation to the pretty
// version.
private abstract static class MappedCodePrinter extends CodeConsumer {
final private Deque<Mapping> mappings;
final private List<Mapping> allMappings;
final private boolean createSrcMap;
final private SourceMap.DetailLevel sourceMapDetailLevel;
protected final StringBuilder code = new StringBuilder(1024);
protected final int lineLengthThreshold;
protected int lineLength = 0;
protected int lineIndex = 0;
MappedCodePrinter(
int lineLengthThreshold,
boolean createSrcMap,
SourceMap.DetailLevel sourceMapDetailLevel) {
Preconditions.checkState(sourceMapDetailLevel != null);
this.lineLengthThreshold = lineLengthThreshold;
this.createSrcMap = createSrcMap;
this.sourceMapDetailLevel = sourceMapDetailLevel;
this.mappings = createSrcMap ? new ArrayDeque<Mapping>() : null;
this.allMappings = createSrcMap ? new ArrayList<Mapping>() : null;
}
/**
* Maintains a mapping from a given node to the position
* in the source code at which its generated form was
* placed. This position is relative only to the current
* run of the CodeConsumer and will be normalized
* later on by the SourceMap.
*
* @see SourceMap
*/
private static class Mapping {
Node node;
Position start;
Position end;
}
/**
* Starts the source mapping for the given
* node
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> at the current position.
*/
@Override
void startSourceMapping(Node node) {
Preconditions.checkState(sourceMapDetailLevel != null);
Preconditions.checkState(node != null);
if (createSrcMap
&& node.getProp(Node.SOURCENAME_PROP) != null
&& node.getLineno() > 0
&& sourceMapDetailLevel.apply(node)) {
int line = getCurrentLineIndex();
int index = getCurrentCharIndex();
Preconditions.checkState(line >= 0);
Mapping mapping = new Mapping();
mapping.node = node;
mapping.start = new Position(line, index);
mappings.push(mapping);
allMappings.add(mapping);
}
}
/**
* Finishes the source mapping for the given
* node at the current position.
*/
@Override
void endSourceMapping(Node node) {
if (createSrcMap && !mappings.isEmpty() && mappings.peek().node == node) {
Mapping mapping = mappings.pop();
int line = getCurrentLineIndex();
int index = getCurrentCharIndex();
Preconditions.checkState(line >= 0);
mapping.end = new Position(line, index);
}
}
/**
* Generates the source map from the given code consumer,
* appending the information it saved to the SourceMap
* object given.
*/
void generateSourceMap(SourceMap map){
if (createSrcMap) {
for (Mapping mapping : allMappings) {
map.addMapping(mapping.node, mapping.start, mapping.end);
}
}
}
/**
* Reports to the code consumer that the given line has been cut at the
* given position (i.e. a \n has been inserted there). All mappings in
* the source maps after that position will be renormalized as needed.
*/
void reportLineCut(int lineIndex, int charIndex) {
if (createSrcMap) {
for (Mapping mapping : allMappings) {
mapping.start = convertPosition(mapping.start, lineIndex, charIndex);
if (mapping.end != null) {
mapping.end = convertPosition(mapping.end, lineIndex, charIndex);
}
}
}
}
/**
* Converts the given position by normalizing it against the insertion
* of a newline at the given line and character position.
*
* @param position The existing position before the newline was inserted.
* @param lineIndex The index of the line at which the newline was inserted.
* @param characterPosition The position on the line at which the newline
* was inserted.
*
* @return The normalized position.
*/
private Position convertPosition(Position position, int lineIndex,
int characterPosition) {
int originalLine = position.getLineNumber();
int originalChar = position.getCharacterIndex();
if (originalLine == lineIndex && originalChar >= characterPosition) {
// If the position falls on the line itself, then normalize it
// if it falls at or
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
indent++;
endLine();
}
@Override
void endCaseBody() {
super.endCaseBody();
indent--;
endStatement();
}
@Override
void appendOp(String op, boolean binOp) {
if (binOp) {
if (getLastChar() != ' ') {
append(" ");
}
append(op);
append(" ");
} else {
append(op);
}
}
/**
* If the body of a for loop or the then clause of an if statement has
* a single statement, should it be wrapped in a block?
* {@inheritDoc}
*/
@Override
boolean shouldPreserveExtraBlocks() {
// When pretty-printing, always place the statement in its own block
// so it is printed on a separate line. This allows breakpoints to be
// placed on the statement.
return true;
}
/**
* @return The TRY node for the specified CATCH node.
*/
private Node getTryForCatch(Node n) {
return n.getParent().getParent();
}
/**
* @return Whether the a line break should be added after the specified
* BLOCK.
*/
@Override
boolean breakAfterBlockFor(Node n, boolean isStatementContext) {
Preconditions.checkState(n.getType() == Token.BLOCK);
Node parent = n.getParent();
if (parent != null) {
int type = parent.getType();
switch (type) {
case Token.DO:
// Don't break before 'while' in DO-WHILE statements.
return false;
case Token.FUNCTION:
// FUNCTIONs are handled separately, don't break here.
return false;
case Token.TRY:
// Don't break before catch
return n != parent.getFirstChild();
case Token.CATCH:
// Don't break before finally
return !NodeUtil.hasFinally(getTryForCatch(parent));
case Token.IF:
// Don't break before else
return n == parent.getLastChild();
}
}
return true;
}
}
static class CompactCodePrinter
extends MappedCodePrinter {
// The CompactCodePrinter tries to emit just enough newlines to stop there
// being lines longer than the threshold. Since the output is going to be
// gzipped, it makes sense to try to make the newlines appear in similar
// contexts so that GZIP can encode them for 'free'.
//
// This version tries to break the lines at 'preferred' places, which are
// between the top-level forms. This works because top level forms tend to
// be more uniform than arbitary legal contexts. Better compression would
// probably require explicit modelling of the gzip algorithm.
private final boolean lineBreak;
private int lineStartPosition = 0;
private int preferredBreakPosition = 0;
/**
* @param lineBreak break the lines a bit more aggressively
* @param lineLengthThreshold The length of a line after which we force
* a newline when possible.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> @param node The root node.
*/
Builder(Node node) {
root = node;
}
/**
* Sets whether pretty printing should be used.
* @param prettyPrint If true, pretty printing will be used.
*/
Builder setPrettyPrint(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
return this;
}
/**
* Sets whether line breaking should be done automatically.
* @param lineBreak If true, line breaking is done automatically.
*/
Builder setLineBreak(boolean lineBreak) {
this.lineBreak = lineBreak;
return this;
}
/**
* Sets whether to output closure-style type annotations.
* @param outputTypes If true, outputs closure-style type annotations.
*/
Builder setOutputTypes(boolean outputTypes) {
this.outputTypes = outputTypes;
return this;
}
/**
* Sets the line length threshold that will be used to determine
* when to break lines, if line breaking is on.
*
* @param threshold The line length threshold.
*/
Builder setLineLengthThreshold(int threshold) {
this.lineLengthThreshold = threshold;
return this;
}
/**
* Sets the source map to which to write the metadata about
* the generated source code.
*
* @param sourceMap The source map.
*/
Builder setSourceMap(SourceMap sourceMap) {
this.sourceMap = sourceMap;
return this;
}
/**
* @param level The detail level to use.
*/
Builder setSourceMapDetailLevel(SourceMap.DetailLevel level) {
Preconditions.checkState(level != null);
this.sourceMapDetailLevel = level;
return this;
}
/**
* Set the charset to use when determining what characters need to be
* escaped in the output.
*/
Builder setOutputCharset(Charset outCharset) {
this.outputCharset = outCharset;
return this;
}
/**
* Generates the source code and returns it.
*/
String build() {
if (root == null) {
throw new IllegalStateException(
"Cannot build without root node being specified");
}
Format outputFormat = outputTypes
? Format.TYPED
: prettyPrint
? Format.PRETTY
: Format.COMPACT;
return toSource(root, outputFormat, lineBreak, lineLengthThreshold,
sourceMap, sourceMapDetailLevel, outputCharset);
}
}
enum Format {
COMPACT,
PRETTY,
TYPED
}
/**
* Converts a tree to js code
*/
private static String toSource(Node root, Format outputFormat,
boolean lineBreak, int lineLengthThreshold,
SourceMap sourceMap,
SourceMap.DetailLevel sourceMapDetailLevel,
Charset outputCharset) {
Preconditions.checkState(sourceMapDetailLevel != null);
boolean createSourceMap = (sourceMap != null);
MappedCodePrinter mcp =
outputFormat == Format.COMPACT
? new CompactCodePrinter(
lineBreak,
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowEquality(p.typeB);
}
};
/**
* Merging function for strict non-equality between types.
*/
private static final
Function<TypePair, TypePair> SHNE =
new Function<TypePair, TypePair>() {
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowInequality(p.typeB);
}
};
/**
* Merging function for inequality comparisons between types.
*/
private final
Function<TypePair, TypePair> INEQ =
new Function<TypePair, TypePair>() {
public TypePair apply(TypePair p) {
return new TypePair(
getRestrictedWithoutUndefined(p.typeA),
getRestrictedWithoutUndefined(p.typeB));
}
};
/**
* Creates a semantic reverse abstract interpreter.
*/
SemanticReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
super(convention, typeRegistry);
}
public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) {
typeOfNode = left;
stringNode = right;
} else if (right.getType() == Token.TYPEOF &&
left.getType() == Token.STRING) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
}
}
switch (operatorToken) {
case Token.AND:
if (outcome
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
}
case Token.EQ:
if (outcome) {
return caseEquality(condition, blindScope, EQ);
} else {
return caseEquality(condition, blindScope, NE);
}
case Token.NE:
if (outcome) {
return caseEquality(condition, blindScope, NE);
} else {
return caseEquality(condition, blindScope, EQ);
}
case Token.SHEQ:
if (outcome) {
return caseEquality(condition, blindScope, SHEQ);
} else {
return caseEquality(condition, blindScope, SHNE);
}
case Token.SHNE:
if (outcome) {
return caseEquality(condition, blindScope, SHNE);
} else {
return caseEquality(condition, blindScope, SHEQ);
}
case Token.NAME:
case Token.GETPROP:
return caseNameOrGetProp(condition, blindScope, outcome);
case Token.ASSIGN:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(),
firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild().getNext(), blindScope, outcome),
outcome);
case Token.NOT:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(), blindScope, !outcome);
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
if (outcome) {
return caseEquality(condition, blindScope, INEQ);
}
break;
case Token.INSTANCEOF:
return caseInstanceOf(
condition.getFirstChild(), condition.getLastChild(), blindScope,
outcome);
case Token.IN:
if (outcome && condition.getFirstChild().getType() == Token.STRING) {
return caseIn(condition.getLastChild(),
condition.getFirstChild().getString(), blindScope);
}
break;
case Token.CASE:
Node left =
condition.getParent().getFirstChild(); // the switch condition
Node right = condition.getFirstChild();
if (outcome) {
return caseEquality(left, right, blindScope, SHEQ);
} else {
return caseEquality(left, right, blindScope, SHNE);
}
}
return nextPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
private FlowScope caseEquality(Node condition, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ToBooleanOutcome(condition);
// creating new scope
if ((leftType != null && leftIsRefineable) ||
(rightType != null && rightIsRefineable)) {
FlowScope informed = blindScope.createChildFlowScope();
if (leftIsRefineable && leftType != null) {
declareNameInScope(informed, left, leftType);
}
if (rightIsRefineable && rightType != null) {
declareNameInScope(informed, right, rightType);
}
return informed;
}
}
return blindScope;
}
private FlowScope caseAndOrMaybeShortCircuiting(Node left, Node right,
FlowScope blindScope, boolean condition) {
FlowScope leftScope = firstPreciserScopeKnowingConditionOutcome(
left, blindScope, !condition);
StaticSlot<JSType> leftVar = leftScope.findUniqueRefinedSlot(blindScope);
if (leftVar == null) {
return blindScope;
}
FlowScope rightScope = firstPreciserScopeKnowingConditionOutcome(
left, blindScope, condition);
rightScope = firstPreciserScopeKnowingConditionOutcome(
right, rightScope, !condition);
StaticSlot<JSType> rightVar = rightScope.findUniqueRefinedSlot(blindScope);
if (rightVar == null || !leftVar.getName().equals(rightVar.getName())) {
return blindScope;
}
JSType type = leftVar.getType().getLeastSupertype(rightVar.getType());
FlowScope informed = blindScope.createChildFlowScope();
informed.inferSlotType(leftVar.getName(), type);
return informed;
}
private FlowScope caseNameOrGetProp(Node name, FlowScope blindScope,
boolean outcome) {
JSType type = getTypeIfRefinable(name, blindScope);
if (type != null) {
JSType restrictedType =
type.getRestrictedTypeGivenToBooleanOutcome(outcome);
FlowScope informed = blindScope.createChildFlowScope();
declareNameInScope(informed, name, restrictedType);
return informed;
}
return blindScope;
}
private FlowScope caseTypeOf(Node node, JSType type, String value,
boolean resultEqualsValue, FlowScope blindScope) {
JSType restrictedType =
getRestrictedByTypeOfResult(type, value, resultEqualsValue);
if (restrictedType == null) {
return blindScope;
}
FlowScope informed = blindScope.createChildFlowScope();
declareNameInScope(informed, node, restrictedType);
return informed;
}
private FlowScope caseInstanceOf(Node left, Node right, FlowScope blindScope,
boolean outcome) {
JSType leftType = getTypeIfRefinable(left, blindScope);
if (leftType == null) {
return blindScope;
}
JSType rightType = right.getJSType();
ObjectType targetType =
typeRegistry.getNativeObjectType(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {
this.compiler = compiler;
}
/**
* Iterate through the nodes, renaming all the labels.
*/
class ProcessLabels implements ScopedCallback {
ProcessLabels() {
// Create a entry for global scope.
namespaceStack.push(new LabelNamespace());
}
// A stack of labels namespaces. Labels in an outer scope aren't part of an
// inner scope, so a new namespace is created each time a scope is entered.
final Deque<LabelNamespace> namespaceStack = Lists.newLinkedList();
// NameGenerator is used to create safe label names.
final NameGenerator nameGenerator =
new NameGenerator(new HashSet<String>(), "", null);
// The list of generated names. Typically, the first name will be "a",
// the second "b", etc.
final ArrayList<String> names = new ArrayList<String>();
@Override
public void enterScope(NodeTraversal nodeTraversal) {
// Start a new namespace for label names.
namespaceStack.push(new LabelNamespace());
}
@Override
public void exitScope(NodeTraversal nodeTraversal) {
namespaceStack.pop();
}
/**
* shouldTraverse is call when descending into the Node tree, so it is used
* here to build the context for label renames.
*
* {@inheritDoc}
*/
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node node,
Node parent) {
if (node.getType() == Token.LABEL) {
// Determine the new name for this label.
LabelNamespace current = namespaceStack.peek();
int currentDepth = current.renameMap.size() + 1;
String name = node.getFirstChild().getString();
// Store the context for this label name.
LabelInfo li = new LabelInfo(currentDepth);
Preconditions.checkState(!current.renameMap.containsKey(name));
current.renameMap.put(name, li);
// Create a new name, if needed, for this depth.
if (names.size() < currentDepth) {
names.add(nameGenerator.generateNextName());
}
String newName = getNameForId(currentDepth);
compiler.addToDebugLog("label renamed: " + name + " => " + newName);
}
return true;
}
/**
* Delegate the actual processing of the node to visitLabel and
* visitBreakOrContinue.
*
* {@inheritDoc}
*/
public void visit(NodeTraversal nodeTraversal, Node node, Node parent) {
switch (node.getType()) {
case Token.LABEL:
visitLabel(node, parent);
break;
case Token.BREAK:
case Token.CONTINUE:
visitBreakOrContinue(node);
break;
}
}
/**
* Rename label references in breaks and continues.
* @param node The break or continue node.
*/
private void visitBreakOrContinue(Node node) {
Node nameNode = node.getFirstChild();
if (nameNode != null) {
// This is a named break or continue;
String name = nameNode.getString();
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Preconditions.checkState(name.length() != 0);
LabelInfo li = getLabelInfo(name);
if (li != null) {
String newName = getNameForId(li.id);
// Mark the label as referenced so it isn't removed.
li.referenced = true;
if (!name.equals(newName)) {
// Give it the short name.
nameNode.setString(newName);
compiler.reportCodeChange();
}
}
}
}
/**
* Rename or remove labels.
* @param node The label node.
* @param parent The parent of the label node.
*/
private void visitLabel(Node node, Node parent) {
Node nameNode = node.getFirstChild();
Preconditions.checkState(nameNode != null);
String name = nameNode.getString();
LabelInfo li = getLabelInfo(name);
// This is a label...
if (li.referenced) {
String newName = getNameForId(li.id);
if (!name.equals(newName)) {
// ... and it is used, give it the short name.
nameNode.setString(newName);
compiler.reportCodeChange();
}
} else {
// ... and it is not referenced, just remove it.
Node newChild = node.getLastChild();
node.removeChild(newChild);
parent.replaceChild(node, newChild);
if (newChild.getType() == Token.BLOCK) {
NodeUtil.tryMergeBlock(newChild);
}
compiler.reportCodeChange();
}
// Remove the label from the current stack of labels.
namespaceStack.peek().renameMap.remove(name);
}
/**
* @param id The id, which is the depth of the label in the current context,
* for which to get a short name.
* @return The short name of the identified label.
*/
String getNameForId(int id) {
return names.get(id - 1);
}
/**
* @param name The name to retrieve information about.
* @return The structure representing the name in the current context.
*/
LabelInfo getLabelInfo(String name) {
return namespaceStack.peek().renameMap.get(name);
}
}
@Override
public void process(Node externs, Node root) {
// Do variable reference counting.
NodeTraversal.traverse(compiler, root, new ProcessLabels());
}
private static class LabelInfo {
boolean referenced = false;
final int id;
LabelInfo(int id) {
this.id = id;
}
}
private static class LabelNamespace {
final Map<String, LabelInfo> renameMap = new HashMap<String, LabelInfo>();
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Checks for non side effecting statements such as
* <pre>
* var s = "this string is "
* "continued on the next line but you forgot the +";
* x == foo(); // should that be '='?
* foo();; // probably just a stray-semicolon. Doesn't hurt to check though
* </p>
* and generates warnings.
*
*/
final class CheckSideEffects extends AbstractPostOrderCallback {
static final DiagnosticType USELESS_CODE_ERROR = DiagnosticType.warning(
"JSC_USELESS_CODE",
"Suspicious code. {0}");
private final CheckLevel level;
CheckSideEffects(CheckLevel level) {
this.level = level;
}
public void visit(NodeTraversal t, Node n, Node parent) {
// VOID nodes appear when there are extra semicolons at the BLOCK level.
// I've been unable to think of any cases where this indicates a bug,
// and apparently some people like keeping these semicolons around,
// so we'll allow it.
if (n.getType() == Token.EMPTY ||
n.getType() == Token.COMMA) {
return;
}
if (parent == null)
return;
int pt = parent.getType();
if (pt == Token.COMMA) {
Node gramps = parent.getParent();
if (gramps.getType() == Token.CALL &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval.
if (n == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
n.getNext().getType() == Token.NAME &&
"eval".equals(n.getNext().getString())) {
return
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
}
}
if (n == parent.getLastChild()) {
for (Node an : parent.getAncestors()) {
int ancestorType = an.getType();
if (ancestorType == Token.COMMA)
continue;
if (ancestorType != Token.EXPR_RESULT &&
ancestorType != Token.BLOCK)
return;
else
break;
}
}
} else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) {
if (pt == Token.FOR && parent.getChildCount() == 4 &&
(n == parent.getFirstChild() ||
n == parent.getFirstChild().getNext().getNext())) {
// Fall through and look for warnings for the 1st and 3rd child
// of a for.
} else {
return; // it might be ok to not have a side-effect
}
}
if (NodeUtil.isSimpleOperatorType(n.getType()) ||
!NodeUtil.mayHaveSideEffects(n, t.getCompiler())) {
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
return;
} else if (NodeUtil.isExpressionNode(n)) {
// we already reported the problem when we visited the child.
return;
}
String msg = "This code lacks side-effects. Is there a bug?";
if (n.getType() == Token.STRING) {
msg = "Is there a missing '+' on the previous line?";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
/**
* Control whether warnings should be restricted or suppressed for specified
* paths.
*
* @author anatol@google.com (Anatol Pomazau)
*/
public class ShowByPathWarningsGuard extends WarningsGuard {
/**
* Controls whether warnings should be restricted to a specified path or
* suppressed within the specified path.
*/
public enum ShowType {
INCLUDE, // Suppress warnings outside the path.
EXCLUDE; // Suppress warnings within the path.
}
private final String[] paths;
private final ShowType showType;
public ShowByPathWarningsGuard(String checkWarningsOnlyForPath) {
this(checkWarningsOnlyForPath, ShowType.INCLUDE);
}
public ShowByPathWarningsGuard(String[] checkWarningsOnlyForPath) {
this(checkWarningsOnlyForPath, ShowType.INCLUDE);
}
public ShowByPathWarningsGuard(String path, ShowType showType) {
this(new String[] { path }, showType);
}
public ShowByPathWarningsGuard(String[] paths, ShowType showType) {
Preconditions.checkArgument(paths != null);
Preconditions.checkArgument(showType != null);
this.paths = paths;
this.showType = showType;
}
@Override
public CheckLevel level(JSError error) {
final String errorPath = error.sourceName;
if (error.level != CheckLevel.ERROR && errorPath != null) {
boolean inPath = false;
for (String path : paths) {
inPath |= errorPath.contains(path);
}
if (inPath ^ (showType == ShowType.INCLUDE)) {
return CheckLevel.OFF;
}
}
return null;
}
@Override
protected int getPriority() {
return WarningsGuard.Priority.FILTER_BY_PATH.value; // applied first
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Get the phase ordering of loops during this run.
* Returns an empty list when the loops are not randomized.
*/
static List<List<String>> getLoopsRun() {
return loopsRun;
}
/**
* Clears the phase ordering of loops during this run.
*/
static void clearLoopsRun() {
loopsRun.clear();
}
/**
* Add the passes generated by the given factories to the compile sequence.
*
* Automatically pulls multi-run passes into fixed point loops. If there
* are 2 or more multi-run passes in a row, they will run together in
* the same fixed point loop. If A and B are in the same fixed point loop,
* the loop will continue to run both A and B until both are finished
* making changes.
*
* Other than that, the PhaseOptimizer is free to tweak the order and
* frequency of multi-run passes in a fixed-point loop.
*/
void consume(List<PassFactory> factories) {
Loop currentLoop = new LoopInternal();
boolean isCurrentLoopPopulated = false;
for (PassFactory factory : factories) {
if (factory.isOneTimePass()) {
if (isCurrentLoopPopulated) {
passes.add(currentLoop);
currentLoop = new LoopInternal();
isCurrentLoopPopulated = false;
}
addOneTimePass(factory);
} else {
currentLoop.addLoopedPass(factory);
isCurrentLoopPopulated = true;
}
}
if (isCurrentLoopPopulated) {
passes.add(currentLoop);
}
}
/**
* Add the pass generated by the given factory to the compile sequence.
* This pass will be run once.
*/
void addOneTimePass(PassFactory factory) {
passes.add(new PassFactoryDelegate(compiler, factory));
}
/**
* Add a loop to the compile sequence. This loop will continue running
* until the AST stops changing.
* @return The loop structure. Pass suppliers should be added to the loop.
*/
Loop addFixedPointLoop() {
Loop loop = new LoopInternal();
passes.add(loop);
return loop;
}
/**
* Adds a sanity checker to be run after every pass. Intended for development.
*/
void setSanityCheck(PassFactory sanityCheck) {
this.sanityCheck = sanityCheck;
}
/**
* Run all the passes in the optimizer.
*/
public void process(Node externs, Node root) {
for (CompilerPass pass : passes) {
pass.process(externs, root);
if (hasHaltingErrors()) {
return;
}
}
}
/**
* Marks the beginning of a pass.
*/
private void startPass(String passName) {
Preconditions.checkState(currentTracer == null && currentPassName == null);
currentPassName = passName;
currentTracer = newTracer(passName);
}
/**
* Marks
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> the end of a pass.
*/
private void endPass(Node externs, Node root) {
Preconditions.checkState(currentTracer != null && currentPassName != null);
String passToCheck = currentPassName;
try {
stopTracer(currentTracer, currentPassName);
currentPassName = null;
currentTracer = null;
maybeSanityCheck(externs, root);
} catch (Exception e) {
// TODO(johnlenz): Remove this once the normalization checks report
// errors instead of exceptions.
throw new RuntimeException("Sanity check failed for " + passToCheck, e);
}
}
/**
* Runs the sanity check if it is available.
*/
void maybeSanityCheck(Node externs, Node root) {
if (sanityCheck != null) {
sanityCheck.create(compiler).process(externs, root);
}
}
private boolean hasHaltingErrors() {
return compiler.hasHaltingErrors();
}
/**
* Returns a new tracer for the given pass name.
*/
private Tracer newTracer(String passName) {
String comment = passName +
(recentChange.hasCodeChanged() ? " on recently changed AST" : "");
if (tracker != null) {
tracker.recordPassStart(passName);
}
return new Tracer("JSCompiler", comment);
}
private void stopTracer(Tracer t, String passName) {
long result = t.stop();
if (tracker != null) {
tracker.recordPassStop(passName, result);
}
}
/**
* A single compiler pass.
*/
private abstract class NamedPass implements CompilerPass {
private final String name;
NamedPass(String name) {
this.name = name;
}
public void process(Node externs, Node root) {
logger.info(name);
startPass(name);
processInternal(externs, root);
endPass(externs, root);
}
abstract void processInternal(Node externs, Node root);
}
/**
* Delegates to a PassFactory for processing.
*/
private class PassFactoryDelegate extends NamedPass {
private final AbstractCompiler myCompiler;
private final PassFactory factory;
private PassFactoryDelegate(
AbstractCompiler myCompiler, PassFactory factory) {
super(factory.getName());
this.myCompiler = myCompiler;
this.factory = factory;
}
@Override
void processInternal(Node externs, Node root) {
factory.create(myCompiler).process(externs, root);
}
}
/**
* Runs a set of compiler passes until they reach a fixed point.
*/
static abstract class Loop implements CompilerPass {
abstract void addLoopedPass(PassFactory factory);
}
/**
* Runs a set of compiler passes until they reach a fixed point.
*
* Notice that this is a non-static class, because it includes the closure
* of PhaseOptimizer.
*/
private class LoopInternal extends Loop {
private final List<
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>NamedPass> myPasses = Lists.newArrayList();
private final Set<String> myNames = Sets.newHashSet();
@Override
void addLoopedPass(PassFactory factory) {
String name = factory.getName();
Preconditions.checkArgument(
!myNames.contains(name),
"Already a pass with name '" + name + "' in this loop");
myNames.add(factory.getName());
myPasses.add(new PassFactoryDelegate(compiler, factory));
}
/**
* Gets the pass names, in order.
*/
private List<String> getPassOrder() {
List<String> order = Lists.newArrayList();
for (NamedPass pass : myPasses) {
order.add(pass.name);
}
return order;
}
public void process(Node externs, Node root) {
Preconditions.checkState(!loopMutex, "Nested loops are forbidden");
loopMutex = true;
if (randomizeLoops) {
randomizePasses();
} else {
optimizePasses();
}
try {
// TODO(nicksantos): Use a smarter algorithm that dynamically adjusts
// the order that passes are run in.
int count = 0;
out: do {
if (count++ > MAX_LOOPS) {
compiler.throwInternalError(OPTIMIZE_LOOP_ERROR, null);
}
recentChange.reset(); // reset before this round of optimizations
for (CompilerPass pass : myPasses) {
pass.process(externs, root);
if (hasHaltingErrors()) {
break out;
}
}
} while (recentChange.hasCodeChanged() && !hasHaltingErrors());
if (randomizeLoops) {
loopsRun.add(getPassOrder());
}
} finally {
loopMutex = false;
}
}
/** Re-arrange the passes in a random order. */
private void randomizePasses() {
List<NamedPass> mixedupPasses = Lists.newArrayList();
Random random = new Random();
while (myPasses.size() > 0) {
mixedupPasses.add(
myPasses.remove(random.nextInt(myPasses.size())));
}
myPasses.addAll(mixedupPasses);
}
/** Re-arrange the passes in an optimal order. */
private void optimizePasses() {
// It's important that this ordering is deterministic, so that
// multiple compiles with the same input produce exactly the same
// results.
//
// To do this, grab any passes we recognize, and move them to the end
// in an "optimal" order.
List<NamedPass> optimalPasses = Lists.newArrayList();
for (String passName : OPTIMAL_ORDER) {
for (NamedPass pass : myPasses) {
if (pass.name.equals(passName)) {
optimalPasses.add(pass);
break;
}
}
}
myPasses.removeAll(optimalP
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>N, E>> nodeList = Lists.newArrayList();
for (DiGraphEdge<N, E> edge : dNode.getInEdges()) {
nodeList.add(edge.getSource());
}
return nodeList;
}
@Override
public List<DiGraphNode<N, E>> getDirectedSuccNodes(
DiGraphNode<N, E> dNode) {
if (dNode == null) {
throw new IllegalArgumentException(dNode + " is null");
}
List<DiGraphNode<N, E>> nodeList = Lists.newArrayList();
for (DiGraphEdge<N, E> edge : dNode.getOutEdges()) {
nodeList.add(edge.getDestination());
}
return nodeList;
}
@Override
public List<GraphvizEdge> getGraphvizEdges() {
List<GraphvizEdge> edgeList = Lists.newArrayList();
for (LinkedDirectedGraphNode<N, E> node : nodes.values()) {
for (DiGraphEdge<N, E> edge : node.getOutEdges()) {
edgeList.add((LinkedDirectedGraphEdge<N, E>) edge);
}
}
return edgeList;
}
@Override
public List<GraphvizNode> getGraphvizNodes() {
List<GraphvizNode> nodeList =
Lists.newArrayListWithCapacity(nodes.size());
for (LinkedDirectedGraphNode<N, E> node : nodes.values()) {
nodeList.add(node);
}
return nodeList;
}
@Override
public String getName() {
return "LinkedGraph";
}
@Override
public boolean isDirected() {
return true;
}
@Override
public Collection<GraphNode<N, E>> getNodes() {
return Collections.<GraphNode<N, E>>unmodifiableCollection(nodes.values());
}
@Override
public List<GraphNode<N, E>> getNeighborNodes(N value) {
DiGraphNode<N, E> node = getDirectedGraphNode(value);
return getNeighborNodes(node);
}
public List<GraphNode<N, E>> getNeighborNodes(DiGraphNode<N, E> node) {
List<GraphNode<N, E>> result = Lists.newArrayList();
for (Iterator<GraphNode<N, E>> i =
((LinkedDirectedGraphNode<N, E>) node).neighborIterator();i.hasNext();) {
result.add(i.next());
}
return result;
}
@Override
public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) {
LinkedDirectedGraphNode<N, E> node = nodes.get(value);
Preconditions.checkNotNull(node);
return node.neighborIterator();
}
@Override
public List<GraphEdge<N, E>> getEdges() {
List<GraphEdge<N, E>> result = Lists.newArrayList();
for (DiGraphNode<N, E> node : nodes.values
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
private final FunctionInjector injector;
private final boolean blockFunctionInliningEnabled;
private final boolean inlineGlobalFunctions;
private final boolean inlineLocalFunctions;
private SpecializeModule.SpecializationState specializationState;
InlineFunctions(AbstractCompiler compiler,
Supplier<String> safeNameIdSupplier,
boolean inlineGlobalFunctions,
boolean inlineLocalFunctions,
boolean blockFunctionInliningEnabled) {
Preconditions.checkArgument(compiler != null);
Preconditions.checkArgument(safeNameIdSupplier != null);
this.compiler = compiler;
this.inlineGlobalFunctions = inlineGlobalFunctions;
this.inlineLocalFunctions = inlineLocalFunctions;
this.blockFunctionInliningEnabled = blockFunctionInliningEnabled;
this.injector = new FunctionInjector(compiler, safeNameIdSupplier, true);
}
FunctionState getOrCreateFunctionState(String fnName) {
FunctionState fs = fns.get(fnName);
if (fs == null) {
fs = new FunctionState();
fns.put(fnName, fs);
}
return fs;
}
public void enableSpecialization(SpecializeModule.SpecializationState
specializationState) {
this.specializationState = specializationState;
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkState(compiler.isNormalized());
NodeTraversal.traverse(compiler, root, new FindCandidateFunctions());
if (fns.isEmpty()) {
return; // Nothing left to do.
}
NodeTraversal.traverse(compiler, root,
new FindCandidatesReferences(fns, anonFns));
trimCanidatesNotMeetingMinimumRequirements();
if (fns.isEmpty()) {
return; // Nothing left to do.
}
// Store the set of function names eligible for inlining and use this to
// prevent function names from being moved into temporaries during
// expression decomposition. If this movement were allowed it would prevent
// the Inline callback from finding the function calls.
//
// This pass already assumes these are constants, so this is safe for anyone
// using function inlining.
//
Set<String> fnNames = Sets.newHashSet(fns.keySet());
injector.setKnownConstants(fnNames);
trimCanidatesUsingOnCost();
if (fns.isEmpty()) {
return; // Nothing left to do.
}
resolveInlineConflicts();
decomposeExpressions(fnNames);
NodeTraversal.traverse(compiler, root,
new CallVisitor(
fns, anonFns, new Inline(injector, specializationState)));
removeInlinedFunctions();
}
/**
* Find functions that might be inlined.
*/
private class FindCandidateFunctions implements Callback {
private int callsSeen = 0;
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
// Don't traverse into function bodies
// if we aren't inlining local functions.
return inlineLocalFunctions || nodeTraversal.inGlobalScope();
}
public void visit(NodeTraversal t, Node n, Node parent)
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {
if ((t.inGlobalScope() && inlineGlobalFunctions)
|| (!t.inGlobalScope() && inlineLocalFunctions)) {
findNamedFunctions(t, n, parent);
findFunctionExpressions(t, n);
}
}
public void findNamedFunctions(NodeTraversal t, Node n, Node parent) {
if (!NodeUtil.isStatement(n)) {
// There aren't any interesting functions here.
return;
}
switch (n.getType()) {
// Functions expressions in the form of:
// var fooFn = function(x) { return ... }
case Token.VAR:
Preconditions.checkState(n.hasOneChild());
Node nameNode = n.getFirstChild();
if (nameNode.getType() == Token.NAME && nameNode.hasChildren()
&& nameNode.getFirstChild().getType() == Token.FUNCTION) {
maybeAddFunction(new FunctionVar(n), t.getModule());
}
break;
// Named functions
// function Foo(x) { return ... }
case Token.FUNCTION:
Preconditions.checkState(NodeUtil.isStatementBlock(parent)
|| parent.getType() == Token.LABEL);
if (!NodeUtil.isFunctionExpression(n)) {
Function fn = new NamedFunction(n);
maybeAddFunction(fn, t.getModule());
}
break;
}
}
/**
* Find function expressions that are called directly in the form of
* (function(a,b,...){...})(a,b,...)
* or
* (function(a,b,...){...}).call(this,a,b, ...)
*/
public void findFunctionExpressions(NodeTraversal t, Node n) {
switch (n.getType()) {
// Functions expressions in the form of:
// (function(){})();
case Token.CALL:
Node fnNode = null;
if (n.getFirstChild().getType() == Token.FUNCTION) {
fnNode = n.getFirstChild();
} else if (NodeUtil.isFunctionObjectCall(n)) {
Node fnIdentifingNode = n.getFirstChild().getFirstChild();
if (fnIdentifingNode.getType() == Token.FUNCTION) {
fnNode = fnIdentifingNode;
}
}
// If a interesting function was discovered, add it.
if (fnNode != null) {
Function fn = new FunctionExpression(fnNode, callsSeen++);
maybeAddFunction(fn, t.getModule());
anonFns.put(fnNode, fn.getName());
}
break;
}
}
}
/**
* Updates the FunctionState object for the given function. Checks if the
* given function matches the criteria for an inlinable function.
*/
private void maybeAddFunction(Function fn, JSModule module) {
String name = fn.getName();
FunctionState fs = getOrCreateFunctionState(name);
// TODO(johnlenz): Maybe "smarten" FunctionState by adding this logic
// to it?
// If the function
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Don't inline exported functions.
String fnName = fn.getName();
if (compiler.getCodingConvention().isExported(fnName)) {
// TODO(johnlenz): Should we allow internal references to be inlined?
// An exported name can be replaced externally, any inlined instance
// would not reflect this change.
// To allow inlining we need to be able to distinguish between exports
// that are used in a read-only fashion and those that can be replaced
// by external definitions.
return false;
}
// Don't inline this special function
if (RenameProperties.RENAME_PROPERTY_FUNCTION_NAME.equals(fnName)) {
return false;
}
// Don't inline if we are specializing and the function can't be fixed up
if (specializationState != null &&
!specializationState.canFixupFunction(fn.getFunctionNode())) {
return false;
}
Node fnNode = fn.getFunctionNode();
return injector.doesFunctionMeetMinimumRequirements(fnName, fnNode);
}
/**
* @see CallVisitor
*/
private interface CallVisitorCallback {
public void visitCallSite(
NodeTraversal t, Node callNode, Node parent, FunctionState fs);
}
/**
* Visit call sites for functions in functionMap.
*/
private static class CallVisitor extends AbstractPostOrderCallback {
protected CallVisitorCallback callback;
private Map<String, FunctionState> functionMap;
private Map<Node, String> anonFunctionMap;
CallVisitor(Map<String, FunctionState> fns,
Map<Node, String> anonFns,
CallVisitorCallback callback) {
this.functionMap = fns;
this.anonFunctionMap = anonFns;
this.callback = callback;
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
// Function calls
case Token.CALL:
Node child = n.getFirstChild();
String name = null;
// NOTE: The normalization pass insures that local names do not
// collide with global names.
if (child.getType() == Token.NAME) {
name = child.getString();
} else if (child.getType() == Token.FUNCTION) {
name = anonFunctionMap.get(child);
} else if (NodeUtil.isFunctionObjectCall(n)) {
Preconditions.checkState(NodeUtil.isGet(child));
Node fnIdentifingNode = child.getFirstChild();
if (fnIdentifingNode.getType() == Token.NAME) {
name = fnIdentifingNode.getString();
} else if (fnIdentifingNode.getType() == Token.FUNCTION) {
name = anonFunctionMap.get(fnIdentifingNode);
}
}
if (name != null) {
FunctionState fs = functionMap.get(name);
// Only visit call-sites for functions that can be inlined.
if (fs != null) {
callback.visit
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>CallSite(t, n, parent, fs);
}
}
break;
}
}
}
/**
* Find references to functions that are inlinable.
*/
private class FindCandidatesReferences
extends CallVisitor
implements CallVisitorCallback {
FindCandidatesReferences(
Map<String, FunctionState> fns,
Map<Node, String> anonFns) {
super(fns, anonFns, null);
this.callback = this;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
if (n.getType() == Token.NAME) {
checkNameUsage(t, n, parent);
}
}
public void visitCallSite(
NodeTraversal t, Node callNode, Node parent, FunctionState fs) {
maybeAddReference(t, fs, callNode, t.getModule());
}
void maybeAddReference(NodeTraversal t, FunctionState fs,
Node callNode, JSModule module) {
if (!fs.canInline()) {
return;
}
boolean referenceAdded = false;
InliningMode mode = fs.canInlineDirectly()
? InliningMode.DIRECT : InliningMode.BLOCK;
referenceAdded = maybeAddReferenceUsingMode(
t, fs, callNode, module, mode);
if (!referenceAdded &&
mode == InliningMode.DIRECT && blockFunctionInliningEnabled) {
// This reference can not be directly inlined, see if
// block replacement inlining is possible.
mode = InliningMode.BLOCK;
referenceAdded = maybeAddReferenceUsingMode(
t, fs, callNode, module, mode);
}
if (!referenceAdded) {
// Don't try to remove a function if we can't inline all
// the references.
fs.setRemove(false);
}
}
private boolean maybeAddReferenceUsingMode(
NodeTraversal t, FunctionState fs, Node callNode,
JSModule module, InliningMode mode) {
if (specializationState != null) {
// If we're specializing, make sure we can fixup
// the containing function before inlining
Node containingFunction = getContainingFunction(t);
if (containingFunction != null && !specializationState.canFixupFunction(
containingFunction)) {
return false;
}
}
CanInlineResult result = injector.canInlineReferenceToFunction(
t, callNode, fs.getFn().getFunctionNode(),
fs.getNamesToAlias(), mode, fs.getReferencesThis(),
fs.hasInnerFunctions());
if (result != CanInlineResult.NO) {
// Yeah!
boolean decompose =
(result == CanInlineResult.AFTER_DECOMPOSITION);
fs.addReference(new Reference(callNode, module, mode, decompose));
return true;
}
return false;
}
/**
* Find functions that can be inlined.
*/
private void checkNameUsage(NodeTraversal t, Node n, Node parent
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>) {
Preconditions.checkState(n.getType() == Token.NAME);
if (parent.getType() == Token.VAR || parent.getType() == Token.FUNCTION) {
// This is a declaration. Duplicate declarations are handle during
// function candidate gathering.
return;
}
if (parent.getType() == Token.CALL && parent.getFirstChild() == n) {
// This is a normal reference to the function.
return;
}
// Check for a ".call" to the named function:
// CALL
// GETPROP/GETELEM
// NAME
// STRING == "call"
// This-Value
// Function-parameter-1
// ...
if (NodeUtil.isGet(parent)
&& n == parent.getFirstChild()
&& n.getNext().getType() == Token.STRING
&& n.getNext().getString().equals("call")) {
Node gramps = n.getAncestor(2);
if (gramps.getType() == Token.CALL
&& gramps.getFirstChild() == parent) {
// Yep, a ".call".
return;
}
}
// Other refs to a function name remove its candidacy for inlining
String name = n.getString();
FunctionState fs = fns.get(name);
if (fs == null) {
return;
}
// If the name is being assigned to it can not be inlined.
if (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) {
// e.g. bar = something; <== we can't inline "bar"
// so mark the function as uninlinable.
// TODO(johnlenz): Should we just remove it from fns here?
fs.setInline(false);
} else {
// e.g. var fn = bar; <== we can't inline "bar"
// As this reference can't be inlined mark the function as
// unremovable.
fs.setRemove(false);
}
}
}
/**
* Inline functions at the call sites.
*/
private static class Inline implements CallVisitorCallback {
private final FunctionInjector injector;
private final SpecializeModule.SpecializationState specializationState;
Inline(FunctionInjector injector,
SpecializeModule.SpecializationState specializationState) {
this.injector = injector;
this.specializationState = specializationState;
}
public void visitCallSite(
NodeTraversal t, Node callNode, Node parent, FunctionState fs) {
Preconditions.checkState(fs.hasExistingFunctionDefinition());
if (fs.canInline()) {
Reference ref = fs.getReference(callNode);
// There are two cases ref can be null: if the call site was introduce
// because it was part of a function that was inlined during this pass
// or if the call site was trimmed from the list of references because
// the function couldn't be inlined at this location.
if (ref != null) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> }
return true;
}
/**
* @return Whether inlining the function reduces code size.
*/
private boolean inliningLowersCost(FunctionState fs) {
return injector.inliningLowersCost(
fs.getModule(),
fs.getFn().getFunctionNode(),
fs.getReferences(),
fs.getNamesToAlias(),
fs.canRemove(),
fs.getReferencesThis());
}
/**
* Size base inlining calculations are thrown off when a function that is
* being inlined also contains calls to functions that are slated for
* inlining.
*
* Specifically, a clone of the FUNCTION node tree is used when the function
* is inlined. Calls in this new tree are not included in the list of function
* references so they won't be inlined (which is what we want). Here we mark
* those functions as non-removable (as they will have new references in the
* cloned node trees).
*
* This prevents a function that would only be inlined because it is
* referenced once from being inlined into multiple call sites because
* the calling function has been inlined in multiple locations or the
* function being removed while there are still references.
*/
private void resolveInlineConflicts() {
for (FunctionState fs : fns.values()) {
resolveInlineConflictsForFunction(fs);
}
}
/**
* @see #resolveInlineConflicts
*/
private void resolveInlineConflictsForFunction(FunctionState fs) {
// Functions that aren't referenced don't cause conflicts.
if (!fs.hasReferences()) {
return;
}
Node fnNode = fs.getFn().getFunctionNode();
Set<String> names = findCalledFunctions(fnNode);
if (!names.isEmpty()) {
// Prevent the removal of the referenced functions.
for (String name : names) {
FunctionState fsCalled = fns.get(name);
if (fsCalled != null && fsCalled.canRemove()) {
fsCalled.setRemove(false);
// For functions that can no longer be removed, check if they should
// still be inlined.
if (!mimimizeCost(fsCalled)) {
// It can't be inlined remove it from the list.
fsCalled.setInline(false);
}
}
}
// Make a copy of the Node, so it isn't changed by other inlines.
fs.setSafeFnNode(fs.getFn().getFunctionNode().cloneTree());
}
}
/**
* This functions that may be called directly.
*/
private Set<String> findCalledFunctions(Node node) {
Set<String> changed = Sets.newHashSet();
findCalledFunctions(node, changed);
return changed;
}
/**
* @see #findCalledFunctions(Node)
*/
private void findCalledFunctions(
Node node, Set<String> changed) {
Preconditions.checkArgument(changed != null);
// For each referenced function, add a new reference
if (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>node.getType() == Token.CALL) {
Node child = node.getFirstChild();
if (child.getType() == Token.NAME) {
String name = child.getString();
changed.add(name);
}
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
findCalledFunctions(c, changed);
}
}
/**
* For any call-site that needs it, prepare the call-site for inlining
* by rewriting the containing expression.
*/
private void decomposeExpressions(Set<String> fnNames) {
ExpressionDecomposer decomposer = new ExpressionDecomposer(
compiler, compiler.getUniqueNameIdSupplier(), fnNames);
for (FunctionState fs : fns.values()) {
if (fs.canInline()) {
for (Reference ref : fs.getReferences()) {
if (ref.requiresDecomposition) {
decomposer.maybeDecomposeExpression(ref.callNode);
}
}
}
}
}
/**
* Removed inlined functions that no longer have any references.
*/
void removeInlinedFunctions() {
for (FunctionState fs : fns.values()) {
if (fs.canRemove()) {
Function fn = fs.getFn();
Preconditions.checkState(fs.canInline());
Preconditions.checkState(fn != null);
verifyAllReferencesInlined(fs);
if (specializationState != null) {
specializationState.reportRemovedFunction(
fn.getFunctionNode(), fn.getDeclaringBlock());
}
fn.remove();
compiler.reportCodeChange();
}
}
}
/**
* Sanity check to verify, that expression rewriting didn't
* make a call inaccessible.
*/
void verifyAllReferencesInlined(FunctionState fs) {
for (Reference ref : fs.getReferences()) {
if (!ref.inlined) {
throw new IllegalStateException("Call site missed.\n call: "
+ ref.callNode.toStringTree() + "\n parent: "
+ ref.callNode.getParent().toStringTree());
}
}
}
/**
* Use to track the decisions that have been make about a function.
*/
private static class FunctionState {
private Function fn = null;
private Node safeFnNode = null;
private boolean inline = true;
private boolean remove = true;
private boolean inlineDirectly = false;
private boolean referencesThis = false;
private boolean hasInnerFunctions = false;
private Map<Node, Reference> references = null;
private JSModule module = null;
private Set<String> namesToAlias = null;
boolean hasExistingFunctionDefinition() {
return (fn != null);
}
public void setReferencesThis(boolean referencesThis) {
this.referencesThis = referencesThis;
}
public boolean getReferencesThis() {
return this.referencesThis;
}
public void setHasInnerFunctions(boolean hasInnerFunctions) {
this.hasInnerFunctions = hasInnerFunctions
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
}
public boolean hasInnerFunctions() {
return hasInnerFunctions;
}
void removeBlockInliningReferences() {
Iterator<Entry<Node, Reference>> i;
for (i = getReferencesInternal().entrySet().iterator(); i.hasNext();) {
Entry<Node, Reference> entry = i.next();
if (entry.getValue().mode == InliningMode.BLOCK) {
i.remove();
}
}
}
public boolean hasBlockInliningReferences() {
for (Reference r : getReferencesInternal().values()) {
if (r.mode == InliningMode.BLOCK) {
return true;
}
}
return false;
}
public Function getFn() {
return fn;
}
public void setFn(Function fn) {
Preconditions.checkState(this.fn == null);
this.fn = fn;
}
public Node getSafeFnNode() {
return (safeFnNode != null) ? safeFnNode : fn.getFunctionNode();
}
public void setSafeFnNode(Node safeFnNode) {
this.safeFnNode = safeFnNode;
}
public boolean canInline() {
return inline;
}
public void setInline(boolean inline) {
this.inline = inline;
if (inline == false) {
// No need to keep references to function that can't be inlined.
references = null;
// Don't remove functions that we aren't inlining.
remove = false;
}
}
public boolean canRemove() {
return remove;
}
public void setRemove(boolean remove) {
this.remove = remove;
}
public boolean canInlineDirectly() {
return inlineDirectly;
}
public void inlineDirectly(boolean directReplacement) {
this.inlineDirectly = directReplacement;
}
public boolean hasReferences() {
return (references != null && !references.isEmpty());
}
private Map<Node, Reference> getReferencesInternal() {
if (references == null) {
return Collections.emptyMap();
}
return references;
}
public void addReference(Reference ref) {
if (references == null) {
references = Maps.newHashMap();
}
references.put(ref.callNode, ref);
}
public Collection<Reference> getReferences() {
return getReferencesInternal().values();
}
public Reference getReference(Node n) {
return getReferencesInternal().get(n);
}
public Set<String> getNamesToAlias() {
if (namesToAlias == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(namesToAlias);
}
public void setNamesToAlias(Set<String> names) {
namesToAlias = names;
}
public void setModule(JSModule module) {
this.module = module;
}
public JSModule getModule() {
return module;
}
}
/**
* Interface for dealing with function declarations and function
* expressions equally
*/
private
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.ExpressionDecomposer.DecompositionType;
import com.google.javascript.jscomp.MakeDeclaredNamesUnique.ContextualRenamer;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* A set of utility functions that replaces CALL with a specified
* FUNCTION body, replacing and aliasing function parameters as
* necessary.
*
* @author johnlenz@google.com (John Lenz)
*/
class FunctionInjector {
private final AbstractCompiler compiler;
private final Supplier<String> safeNameIdSupplier;
private final boolean allowDecomposition;
private Set<String> knownConstants = Sets.newHashSet();
/**
* @param allowDecomposition Whether an effort should be made to break down
* expressions into simpler expressions to allow functions to be injected
* where they would otherwise be disallowed.
*/
public FunctionInjector(
AbstractCompiler compiler,
Supplier<String> safeNameIdSupplier,
boolean allowDecomposition) {
Preconditions.checkNotNull(compiler);
Preconditions.checkNotNull(safeNameIdSupplier);
this.compiler = compiler;
this.safeNameIdSupplier = safeNameIdSupplier;
this.allowDecomposition = allowDecomposition;
}
/** The type of inlining to perform. */
enum InliningMode {
/**
* Directly replace the call expression. Only functions of meeting
* strict preconditions can be inlined.
*/
DIRECT,
/**
* Replaces the call expression with a block of statements. Conditions
* on the function are looser in mode, but stricter on the call site.
*/
BLOCK
}
/** Holds a reference to the call node of a function call */
static class Reference {
final Node callNode;
final JSModule module;
final InliningMode mode;
Reference(Node callNode, JSModule module, InliningMode mode){
this.callNode = callNode;
this.module = module;
this.mode = mode;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Introducing
// an inner function into another function can capture a variable and cause
// a memory leak. This isn't a problem in the global scope as those values
// last until explicitly cleared.
if (containsFunctions && !t.inGlobalScope()) {
// TODO(johnlenz): Allow inlining into any scope without local names or
// inner functions.
return CanInlineResult.NO;
}
// TODO(johnlenz): Add support for 'apply'
if (referencesThis && !NodeUtil.isFunctionObjectCall(callNode)) {
// TODO(johnlenz): Allow 'this' references to be replaced with a
// global 'this' object.
return CanInlineResult.NO;
}
if (mode == InliningMode.DIRECT) {
return canInlineReferenceDirectly(callNode, fnNode);
} else {
return canInlineReferenceAsStatementBlock(
t, callNode, fnNode, needAliases);
}
}
/**
* Only ".call" calls and direct calls to functions are supported.
* @param callNode The call evaluate.
* @return Whether the call is of a type that is supported.
*/
private boolean isSupportedCallType(Node callNode) {
if (callNode.getFirstChild().getType() != Token.NAME) {
if (NodeUtil.isFunctionObjectCall(callNode)) {
Node thisValue = callNode.getFirstChild().getNext();
if (thisValue == null || thisValue.getType() != Token.THIS) {
return false;
}
} else if (NodeUtil.isFunctionObjectApply(callNode)) {
return false;
}
}
return true;
}
/**
* Inline a function into the call site.
*/
Node inline(
NodeTraversal t, Node callNode, String fnName, Node fnNode,
InliningMode mode) {
Preconditions.checkState(compiler.isNormalized());
if (mode == InliningMode.DIRECT) {
return inlineReturnValue(callNode, fnNode);
} else {
return inlineFunction(callNode, fnNode, fnName);
}
}
/**
* Inline a function that fulfills the requirements of
* canInlineReferenceDirectly into the call site, replacing only the CALL
* node.
*/
private Node inlineReturnValue(Node callNode, Node fnNode) {
Node block = fnNode.getLastChild();
Node callParentNode = callNode.getParent();
// NOTE: As the normalize pass guarantees globals aren't being
// shadowed and an expression can't introduce new names, there is
// no need to check for conflicts.
// Create an argName -> expression map, checking for side effects.
Map<String, Node> argMap =
FunctionArgumentInjector.getFunctionCallParameterMap(
fnNode, callNode, this.safeNameIdSupplier);
Node newExpression;
if (!block.hasChildren()) {
Node srcLocation = block;
newExpression = NodeUtil.newUndefinedNode(srcLocation);
} else {
Node returnNode =
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> block.getFirstChild();
Preconditions.checkArgument(returnNode.getType() == Token.RETURN);
// Clone the return node first.
Node safeReturnNode = returnNode.cloneTree();
Node inlineResult = FunctionArgumentInjector.inject(
safeReturnNode, null, argMap);
Preconditions.checkArgument(safeReturnNode == inlineResult);
newExpression = safeReturnNode.removeFirstChild();
}
callParentNode.replaceChild(callNode, newExpression);
return newExpression;
}
/**
* Supported call site types.
*/
private enum CallSiteType {
/**
* Used for a call site for which there does not exist a method
* to inline it.
*/
UNSUPPORTED,
/**
* A call as a statement. For example: "foo();".
* EXPR_RESULT
* CALL
*/
SIMPLE_CALL,
/**
* An assignment, where the result of the call is assigned to a simple
* name. For example: "a = foo();".
* EXPR_RESULT
* NAME A
* CALL
* FOO
*/
SIMPLE_ASSIGNMENT,
/**
* An var declaration and initialization, where the result of the call is
* assigned to the declared name
* name. For example: "a = foo();".
* VAR
* NAME A
* CALL
* FOO
*/
VAR_DECL_SIMPLE_ASSIGNMENT,
/**
* An arbitrary expression, the root of which is a EXPR_RESULT, IF,
* RETURN, SWITCH or VAR. The call must be the first side-effect in
* the expression.
*
* Examples include:
* "if (foo()) {..."
* "return foo();"
* "var a = 1 + foo();"
* "a = 1 + foo()"
* "foo() ? 1:0"
* "foo() && x"
*/
EXPRESSION,
/**
* An arbitrary expression, the root of which is a EXPR_RESULT, IF,
* RETURN, SWITCH or VAR. Where the call is not the first side-effect in
* the expression.
*/
DECOMPOSABLE_EXPRESSION,
}
/**
* Determine which, if any, of the supported types the call site is.
*/
private CallSiteType classifyCallSite(Node callNode) {
Node parent = callNode.getParent();
Node grandParent = parent.getParent();
// Verify the call site:
if (NodeUtil.isExprCall(parent)) {
// This is a simple call? Example: "foo();".
return CallSiteType.SIMPLE_CALL;
} else if (NodeUtil.isExprAssign(grandParent)
&& !NodeUtil.isLhs(callNode, parent)
&& parent.getFirstChild().getType() == Token.NAME
&& !NodeUtil.isConstantName(parent.getFirstChild())) {
// This is a simple
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> assignment. Example: "x = foo();"
return CallSiteType.SIMPLE_ASSIGNMENT;
} else if (parent.getType() == Token.NAME
&& !NodeUtil.isConstantName(parent)
&& grandParent.getType() == Token.VAR
&& grandParent.hasOneChild()) {
// This is a var declaration. Example: "var x = foo();"
// TODO(johnlenz): Should we be checking for constants on the
// left-hand-side of the assignments (and handling them as EXPRESSION?
return CallSiteType.VAR_DECL_SIMPLE_ASSIGNMENT;
} else {
Node expressionRoot = ExpressionDecomposer.findExpressionRoot(callNode);
if (expressionRoot != null) {
ExpressionDecomposer decomposer = new ExpressionDecomposer(
compiler, safeNameIdSupplier, knownConstants);
DecompositionType type = decomposer.canExposeExpression(
callNode);
if (type == DecompositionType.MOVABLE) {
return CallSiteType.EXPRESSION;
} else if (type == DecompositionType.DECOMPOSABLE) {
return CallSiteType.DECOMPOSABLE_EXPRESSION;
} else {
Preconditions.checkState(type == DecompositionType.UNDECOMPOSABLE);
}
}
}
return CallSiteType.UNSUPPORTED;
}
/**
* Inline a function which fulfills the requirements of
* canInlineReferenceAsStatementBlock into the call site, replacing the
* parent expression.
*/
private Node inlineFunction(
Node callNode, Node fnNode, String fnName) {
Node parent = callNode.getParent();
Node grandParent = parent.getParent();
// TODO(johnlenz): Consider storing the callSite classification in the
// reference object and passing it in here.
CallSiteType callSiteType = classifyCallSite(callNode);
Preconditions.checkArgument(callSiteType != CallSiteType.UNSUPPORTED);
// Store the name for the result. This will be used to
// replace "return expr" statements with "resultName = expr"
// to replace
String resultName = null;
boolean needsDefaultReturnResult = true;
switch (callSiteType) {
case SIMPLE_ASSIGNMENT:
resultName = parent.getFirstChild().getString();
break;
case VAR_DECL_SIMPLE_ASSIGNMENT:
resultName = parent.getString();
break;
case SIMPLE_CALL:
resultName = null; // "foo()" doesn't need a result.
needsDefaultReturnResult = false;
break;
case EXPRESSION:
resultName = getUniqueResultName();
needsDefaultReturnResult = false; // The intermediary result already
// has the default value.
break;
case DECOMPOSABLE_EXPRESSION:
throw new IllegalStateException(
"Decomposable expressions must decomposed before inlining.");
default:
throw new IllegalStateException("Unexpected call site type.");
}
boolean isCallInLoop = NodeUtil.isWithinLoop(callNode);
FunctionToBlockMutator mutator = new Function
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ToBlockMutator(
compiler, this.safeNameIdSupplier);
Node newBlock = mutator.mutate(
fnName, fnNode, callNode, resultName,
needsDefaultReturnResult, isCallInLoop);
// TODO(nicksantos): Create a common mutation function that
// can replace either a VAR name assignment, assignment expression or
// a EXPR_RESULT.
Node greatGrandParent = grandParent.getParent();
switch (callSiteType) {
case VAR_DECL_SIMPLE_ASSIGNMENT:
// Remove the call from the name node.
parent.removeChild(parent.getFirstChild());
Preconditions.checkState(parent.getFirstChild() == null);
// Add the call, after the VAR.
greatGrandParent.addChildAfter(newBlock, grandParent);
break;
case SIMPLE_ASSIGNMENT:
// The assignment is now part of the inline function so
// replace it completely.
Preconditions.checkState(NodeUtil.isExpressionNode(grandParent));
greatGrandParent.replaceChild(grandParent, newBlock);
break;
case SIMPLE_CALL:
// If nothing is looking at the result just replace the call.
Preconditions.checkState(NodeUtil.isExpressionNode(parent));
grandParent.replaceChild(parent, newBlock);
break;
case EXPRESSION:
// TODO(johnlenz): Maybe change this so that movable and decomposable
// expressions are handled the same way: The call is moved and
// then handled by one the three basic cases, rather than
// introducing a new case.
Node injectionPoint = ExpressionDecomposer.findInjectionPoint(callNode);
Preconditions.checkNotNull(injectionPoint);
Node injectionPointParent = injectionPoint.getParent();
Preconditions.checkNotNull(injectionPointParent);
Preconditions.checkState(
NodeUtil.isStatementBlock(injectionPointParent));
// Declare the intermediate result name.
newBlock.addChildrenToFront(
NodeUtil.newVarNode(resultName, null)
.copyInformationFromForTree(callNode));
// Inline the function before the selected injection point (before
// the call).
injectionPointParent.addChildBefore(newBlock, injectionPoint);
// Replace the call site with a reference to the intermediate
// result name.
parent.replaceChild(callNode, Node.newString(Token.NAME, resultName));
break;
default:
throw new IllegalStateException("Unexpected call site type.");
}
return newBlock;
}
/**
* Checks if the given function matches the criteria for an inlinable
* function, and if so, adds it to our set of inlinable functions.
*/
boolean isDirectCallNodeReplacementPossible(Node fnNode) {
// Only inline single-statement functions
Node block = NodeUtil.getFunctionBody(fnNode);
// Check if this function is suitable for direct replacement of a CALL node:
// a function that consists of single return that returns an expression.
if (!block.hasChildren()) {
// special case empty functions.
return true;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> } else if (block.hasOneChild()) {
// Only inline functions that return something.
if (block.getFirstChild().getType() == Token.RETURN
&& block.getFirstChild().getFirstChild() != null) {
return true;
}
}
return false;
}
enum CanInlineResult {
YES,
AFTER_DECOMPOSITION,
NO
}
/**
* Determines whether a function can be inlined at a particular call site.
* There are several criteria that the function and reference must hold in
* order for the functions to be inlined:
* - It must be a simple call, or assignment, or var initialization.
* <pre>
* f();
* a = foo();
* var a = foo();
* </pre>
*/
private CanInlineResult canInlineReferenceAsStatementBlock(
NodeTraversal t, Node callNode, Node fnNode, Set<String> namesToAlias) {
CallSiteType callSiteType = classifyCallSite(callNode);
if (callSiteType == CallSiteType.UNSUPPORTED) {
return CanInlineResult.NO;
}
if (!allowDecomposition
&& callSiteType == CallSiteType.DECOMPOSABLE_EXPRESSION) {
return CanInlineResult.NO;
}
if (!callMeetsBlockInliningRequirements(
t, callNode, fnNode, namesToAlias)) {
return CanInlineResult.NO;
}
if (callSiteType == CallSiteType.DECOMPOSABLE_EXPRESSION) {
return CanInlineResult.AFTER_DECOMPOSITION;
} else {
return CanInlineResult.YES;
}
}
/**
* Determines whether a function can be inlined at a particular call site.
* - Don't inline if the calling function contains an inner function and
* inlining would introduce new globals.
*/
private boolean callMeetsBlockInliningRequirements(
NodeTraversal t, Node callNode, Node fnNode, Set<String> namesToAlias) {
// Note: functions that contain function definitions are filtered out
// in isCanidateFunction.
// TODO(johnlenz): Determining if the called function contains VARs
// or if the caller contains inner functions accounts for 20% of the
// runtime cost of this pass.
// Don't inline functions with var declarations into a scope with inner
// functions as the new vars would leak into the inner function and
// cause memory leaks.
boolean fnContainsVars = NodeUtil.has(
NodeUtil.getFunctionBody(fnNode),
new NodeUtil.MatchDeclaration(),
new NodeUtil.MatchShallowStatement());
boolean callerContainsFunction = false;
if (!t.inGlobalScope()) {
Node fnCaller = t.getScopeRoot();
Node fnCallerBody = fnCaller.getLastChild();
callerContainsFunction = NodeUtil.containsFunction(fnCallerBody);
}
if (fnContainsVars && callerContainsFunction) {
return false;
}
// If the caller contains functions, verify we aren't adding any
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> // additional VAR declarations because aliasing is needed.
if (callerContainsFunction) {
Map<String, Node> args =
FunctionArgumentInjector.getFunctionCallParameterMap(
fnNode, callNode, this.safeNameIdSupplier);
boolean hasArgs = !args.isEmpty();
if (hasArgs) {
// Limit the inlining
Set<String> allNamesToAlias = Sets.newHashSet(namesToAlias);
FunctionArgumentInjector.maybeAddTempsForCallArguments(
fnNode, args, allNamesToAlias, compiler.getCodingConvention());
if (!allNamesToAlias.isEmpty()) {
return false;
}
}
}
return true;
}
/**
* Determines whether a function can be inlined at a particular call site.
* There are several criteria that the function and reference must hold in
* order for the functions to be inlined:
* 1) If a call's arguments have side effects,
* the corresponding argument in the function must only be referenced once.
* For instance, this will not be inlined:
* <pre>
* function foo(a) { return a + a }
* x = foo(i++);
* </pre>
*/
private CanInlineResult canInlineReferenceDirectly(
Node callNode, Node fnNode) {
if (!isDirectCallNodeReplacementPossible(fnNode)) {
return CanInlineResult.NO;
}
Node block = fnNode.getLastChild();
// CALL NODE: [ NAME, ARG1, ARG2, ... ]
Node cArg = callNode.getFirstChild().getNext();
// Functions called via 'call' and 'apply' have a this-object as
// the first parameter, but this is not part of the called function's
// parameter list.
if (callNode.getFirstChild().getType() != Token.NAME) {
if (NodeUtil.isFunctionObjectCall(callNode)) {
// TODO(johnlenz): Support replace this with a value.
Preconditions.checkNotNull(cArg);
Preconditions.checkState(cArg.getType() == Token.THIS);
cArg = cArg.getNext();
} else {
// ".apply" call should be filtered before this.
Preconditions.checkState(!NodeUtil.isFunctionObjectApply(callNode));
}
}
// FUNCTION NODE -> LP NODE: [ ARG1, ARG2, ... ]
Node fnParam = NodeUtil.getFnParameters(fnNode).getFirstChild();
while (cArg != null || fnParam != null) {
// For each named parameter check if a mutable argument use more than one.
if (fnParam != null) {
if (cArg != null) {
// Check for arguments that are evaluated more than once.
// Note: Unlike block inlining, there it is not possible that a
// parameter reference will be in a loop.
if (NodeUtil.mayEffectMutableState(cArg)
&& NodeUtil.getNameReferenceCount(
block, fnParam.getString()) > 1) {
return Can
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.ESTIMATED_IDENTIFIER_COST);
Node block = fnNode.getLastChild();
if (!block.hasChildren()) {
// Assume the inline cost is zero for empty functions.
return -costDeltaFunctionOverhead;
}
if (mode == InliningMode.DIRECT) {
// The part of the function that is inlined using direct inlining:
// "return " (7)
return -(costDeltaFunctionOverhead + 7);
} else {
int aliasCount = namesToAlias.size();
// Originally, we estimated purely base on the function code size, relying
// on later optimizations. But that did not produce good results, so here
// we try to estimate the something closer to the actual inlined coded.
// NOTE 1: Result overhead is only if there is an assignment, but
// getting that information would require some refactoring.
// NOTE 2: The aliasing overhead is currently an under-estimate,
// as some parameters are aliased because of the parameters used.
// Perhaps we should just assume all parameters will be aliased?
final int INLINE_BLOCK_OVERHEAD = 4; // "X:{}"
final int PER_RETURN_OVERHEAD = 2; // "return" --> "break X"
final int PER_RETURN_RESULT_OVERHEAD = 3; // "XX="
final int PER_ALIAS_OVERHEAD = 3; // "XX="
// TODO(johnlenz): Counting the number of returns is relatively expensive
// this information should be determined during the traversal and
// cached.
int returnCount = NodeUtil.getNodeTypeReferenceCount(
block, Token.RETURN, new NodeUtil.MatchShallowStatement());
int resultCount = (returnCount > 0) ? returnCount - 1 : 0;
int baseOverhead = (returnCount > 0) ? INLINE_BLOCK_OVERHEAD : 0;
int overhead = baseOverhead
+ returnCount * PER_RETURN_OVERHEAD
+ resultCount * PER_RETURN_RESULT_OVERHEAD
+ aliasCount * PER_ALIAS_OVERHEAD;
return (overhead - costDeltaFunctionOverhead);
}
}
/**
* Store the names of known constants to be used when classifying call-sites
* in expressions.
*/
public void setKnownConstants(Set<String> knownConstants) {
// This is only expected to be set once. The same set should be used
// when evaluating call-sites and inlining calls.
Preconditions.checkState(this.knownConstants.isEmpty());
this.knownConstants = knownConstants;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Prepare the AST before we do any checks or optimizations on it.
*
* This pass must run. It should bring the AST into a consistent state,
* and add annotations where necessary. It should not make any transformations
* on the tree that would lose source information, since we need that source
* information for checks.
*
* @author johnlenz@google.com (John Lenz)
*/
class PrepareAst implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean checkOnly;
PrepareAst(AbstractCompiler compiler) {
this(compiler, false);
}
PrepareAst(AbstractCompiler compiler, boolean checkOnly) {
this.compiler = compiler;
this.checkOnly = checkOnly;
}
private void reportChange() {
if (checkOnly) {
Preconditions.checkState(false, "normalizeNodeType constraints violated");
}
}
@Override
public void process(Node externs, Node root) {
if (checkOnly) {
normalizeNodeTypes(root);
} else {
// Don't perform "PrepareAnnotations" when doing checks as
// they currently aren't valid during sanity checks. In particular,
// they DIRECT_EVAL shouldn't be applied after inlining has been
// performed.
if (externs != null) {
NodeTraversal.traverse(
compiler, externs, new PrepareAnnotations(compiler));
}
if (root != null) {
NodeTraversal.traverse(
compiler, root, new PrepareAnnotations(compiler));
}
}
}
/**
* Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
if (n.getType() == Token.EXPR_VOID) {
n.setType(Token.EXPR_RESULT);
reportChange();
}
// Remove unused properties to minimize differences between ASTs
// produced by the two parsers.
if (n.getType() == Token.FUNCTION) {
Preconditions.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>checkState(n.getProp(Node.FUNCTION_PROP) == null);
}
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& n.getType() != Token.LABEL
&& n.getType() != Token.SWITCH) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
c.getType() != Token.BLOCK) {
Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno());
newBlock.copyInformationFrom(n);
n.replaceChild(c, newBlock);
if (c.getType() != Token.EMPTY) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
implements NodeTraversal.Callback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.OBJECTLIT) {
normalizeObjectLiteralAnnotations(n);
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateFunctions(n, parent);
annotateDispatchers(n, parent);
break;
}
}
private void normalizeObjectLiteralAnnotations(Node objlit) {
Preconditions.checkState(objlit.getType() == Token.OBJECTLIT);
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.getType() == Token.CALL);
// Keep track of of the "this" context of a
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval()".
if (first.getType() == Token.NAME &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.getType() == Token.ASSIGN) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between jsdoc on the object literal node and jsdoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
private void normalizeObjectLiteralKeyAnnotations(
Node objlit, Node key, Node value) {
Preconditions.checkState(objlit.getType() == Token.OBJECTLIT);
if (key.getJSDocInfo() != null &&
value.getType() == Token.FUNCTION) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
/**
* Annotate optional and var_arg function parameters.
*/
private void annotateFunctions(Node n, Node parent) {
JSDocInfo fnInfo = NodeUtil.getFunctionInfo(n);
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter(arg) ||
typeExpr != null && typeExpr.isVarArgs()) {
arg.put
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> of superclass type",
superObject, declaredSuper);
}
// Correct the super type.
if (!subCtor.hasCachedValues()) {
subCtor.setPrototypeBasedOn(superObject);
}
}
}
/**
* Expect that the first type can be cast to the second type. The first type
* should be either a subtype or supertype of the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) {
castType = castType.restrictByNotNullOrUndefined();
type = type.restrictByNotNullOrUndefined();
if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) {
if (shouldReport) {
compiler.report(
t.makeError(n, INVALID_CAST,
castType.toString(), type.toString()));
}
registerMismatch(type, castType);
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
*/
void expectUndeclaredVariable(String sourceName, Node n, Node parent, Var var,
String variableName, JSType newType) {
boolean allowDupe = false;
if (n.getType() == Token.GETPROP) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
// compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
// native type.
if (var.input == null) {
n.setJSType(varType);
if (parent.getType() == Token.VAR) {
if (n.getFirstChild() !=
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.getType() == Token.FUNCTION);
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().getType() == Token.EXPR_RESULT) ||
!newType.equals(varType)) {
if (shouldReport) {
compiler.report(
JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
}
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented.
*/
void expectAllInterfacePropertiesImplemented(FunctionType type) {
ObjectType instance = type.getInstanceType();
for (ObjectType implemented : type.getAllImplementedInterfaces()) {
if (implemented.getImplicitPrototype() != null) {
for (String prop :
implemented.getImplicitPrototype().getOwnPropertyNames()) {
if (!instance.hasProperty(prop)) {
Node source = type.getSource();
Preconditions.checkNotNull(source);
String sourceName = (String) source.getProp(Node.SOURCENAME_PROP);
sourceName = sourceName == null ? "" : sourceName;
if (shouldReport) {
compiler.report(JSError.make(sourceName, source,
INTERFACE_METHOD_NOT_IMPLEMENTED,
prop, implemented.toString(), instance.toString()));
}
registerMismatch(instance, implemented);
}
}
}
}
}
/**
* Report a type mismatch
*/
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSType required) {
mismatch(t.getSourceName(), n, msg, found, required);
}
private void mismatch(NodeTraversal t, Node n,
String msg, JSType found, JSTypeNative required) {
mismatch(t, n, msg, found, getNativeType(required));
}
private void mismatch(String sourceName, Node n,
String msg, JSType found, JSType required) {
registerMismatch(found, required);
if (shouldReport) {
compiler.report(
JSError.make(sourceName, n, TYPE_MISMATCH_WARNING,
formatFoundRequired(msg, found, required)));
}
}
private void registerMismatch(JSType found, JSType required) {
// Don't register a mismatch for differences in null or undefined or if the
// code didn't downcast.
found = found.restrictByNotNullOrUndefined();
required = required.restrictByNotNullOrUndefined();
if (found.canAssignTo(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>required) || required.canAssignTo(found)) {
return;
}
mismatches.add(new TypeMismatch(found, required));
if (found instanceof FunctionType &&
required instanceof FunctionType) {
FunctionType fnTypeA = ((FunctionType) found);
FunctionType fnTypeB = ((FunctionType) required);
Iterator<Node> paramItA = fnTypeA.getParameters().iterator();
Iterator<Node> paramItB = fnTypeB.getParameters().iterator();
while (paramItA.hasNext() && paramItB.hasNext()) {
registerIfMismatch(paramItA.next().getJSType(),
paramItB.next().getJSType());
}
registerIfMismatch(fnTypeA.getReturnType(), fnTypeB.getReturnType());
}
}
private void registerIfMismatch(JSType found, JSType required) {
if (found != null && required != null &&
!found.canAssignTo(required)) {
registerMismatch(found, required);
}
}
/**
* Formats a found/required error message.
*/
private String formatFoundRequired(String description, JSType found,
JSType required) {
return MessageFormat.format(FOUND_REQUIRED, description, found, required);
}
/**
* Given a node, get a human-readable name for the type of that node so
* that will be easy for the programmer to find the original declaration.
*
* For example, if SubFoo's property "bar" might have the human-readable
* name "Foo.prototype.bar".
*
* @param n The node.
* @param dereference If true, the type of the node will be dereferenced
* to an Object type, if possible.
*/
String getReadableJSTypeName(Node n, boolean dereference) {
// If we're analyzing a GETPROP, the property may be inherited by the
// prototype chain. So climb the prototype chain and find out where
// the property was originally defined.
if (n.getType() == Token.GETPROP) {
ObjectType objectType = getJSType(n.getFirstChild()).dereference();
if (objectType != null) {
String propName = n.getLastChild().getString();
while (objectType != null && !objectType.hasOwnProperty(propName)) {
objectType = objectType.getImplicitPrototype();
}
// Don't show complex function names or anonymous types.
// Instead, try to get a human-readable type name.
if (objectType != null &&
(objectType.getConstructor() != null ||
objectType.isFunctionPrototypeType())) {
return objectType.toString() + "." + propName;
}
}
}
JSType type = getJSType(n);
if (dereference) {
ObjectType dereferenced = type.dereference();
if (dereferenced != null) {
type = dereferenced;
}
}
String qualifiedName = n.getQualifiedName();
if (type.isFunctionPrototypeType() ||
(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>FlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, false);
cfa.process(null, scope.getRootNode());
cfgStack.push(curCfg);
curCfg = cfa.getCfg();
new GraphReachability<Node, ControlFlowGraph.Branch>(curCfg)
.compute(curCfg.getEntry().getValue());
}
@Override
public void exitScope(NodeTraversal t) {
curCfg = cfgStack.pop();
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (parent == null) {
return;
}
if (n.getType() == Token.FUNCTION || n.getType() == Token.SCRIPT) {
return;
}
// Removes TRYs that had its CATCH removed and/or empty FINALLY.
// TODO(dcc): Move the parts of this that don't require a control flow
// graph to PeepholeRemoveDeadCode
if (n.getType() == Token.TRY) {
Node body = n.getFirstChild();
Node catchOrFinallyBlock = body.getNext();
Node finallyBlock = catchOrFinallyBlock.getNext();
if (!catchOrFinallyBlock.hasChildren() &&
(finallyBlock == null || !finallyBlock.hasChildren())) {
n.removeChild(body);
parent.replaceChild(n, body);
compiler.reportCodeChange();
n = body;
}
}
DiGraphNode<Node, Branch> gNode = curCfg.getDirectedGraphNode(n);
if (gNode == null) { // Not in CFG.
return;
}
if (gNode.getAnnotation() != GraphReachability.REACHABLE ||
(removeNoOpStatements && !NodeUtil.mayHaveSideEffects(n))) {
removeDeadExprStatementSafely(n);
return;
}
tryRemoveUnconditionalBranching(n);
}
/**
* Tries to remove n if an unconditional branch node (break, continue or
* return) if the target of n is the same as the the follow of n. That is, if
* we remove n, the control flow remains the same. Also if n targets to
* another unconditional branch, this function will recursively try to remove
* the target branch as well. The reason why we want to cascade this removal
* is because we only run this pass once. If we have code such as
*
* break -> break -> break
*
* where all 3 break's are useless. The order of removal matters. When we
* first look at the first break, we see that it branches to the 2nd break.
* However, if we remove the last break, the 2nd break becomes useless and
* finally the first break becomes useless as well.
*
* @return The target of this jump. If the target is also useless jump,
* the target of that useless jump recursively
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.
*/
@SuppressWarnings("fallthrough")
private Node tryRemoveUnconditionalBranching(Node n) {
/*
* For each of the unconditional branching control flow node, check to see
* if the ControlFlowAnalysis.computeFollowNode of that node is same as
* the branching target. If it is, the branch node is safe to be removed.
*
* This is not as clever as MinimizeExitPoints because it doesn't do any
* if-else conversion but it handles more complicated switch statements
* much nicer.
*/
// If n is null the target is the end of the function, nothing to do.
if (n == null) {
return n;
}
DiGraphNode<Node, Branch> gNode = curCfg.getDirectedGraphNode(n);
if (gNode == null) {
return n;
}
// If the parent is null, this mean whatever node it was there is now
// useless and it has been removed by other logics in this pass. That node
// while no longer exists in the AST, is still in the CFG because we
// never update the graph as nodes are removed.
if (n.getParent() == null) {
List<DiGraphEdge<Node,Branch>> outEdges = gNode.getOutEdges();
if (outEdges.size() == 1) {
return tryRemoveUnconditionalBranching(
outEdges.get(0).getDestination().getValue());
}
}
switch (n.getType()) {
case Token.BLOCK:
if (n.hasChildren()) {
Node first = n.getFirstChild();
return tryRemoveUnconditionalBranching(first);
} else {
return tryRemoveUnconditionalBranching(
ControlFlowAnalysis.computeFollowNode(n));
}
case Token.RETURN:
if (n.hasChildren()) {
break;
}
case Token.BREAK:
case Token.CONTINUE:
// We are looking for a control flow changing statement that always
// branches to the same node. If removing it the control flow still
// branches to that same node. It is safe to remove it.
List<DiGraphEdge<Node,Branch>> outEdges = gNode.getOutEdges();
if (outEdges.size() == 1 &&
// If there is a next node, there is no chance this jump is useless.
(n.getNext() == null || n.getNext().getType() == Token.FUNCTION)) {
Preconditions.checkState(outEdges.get(0).getValue() == Branch.UNCOND);
Node fallThrough = tryRemoveUnconditionalBranching(
ControlFlowAnalysis.computeFollowNode(n));
Node nextCfgNode = outEdges.get(0).getDestination().getValue();
if (nextCfgNode == fallThrough) {
removeDeadExprStatementSafely(n);
return fallThrough;
}
}
}
return n;
}
private void removeDeadExprStatementSafely(Node n) {
if (n.getType() == Token.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>EMPTY ||
(n.getType() == Token.BLOCK && !n.hasChildren())) {
// Not always trivial to remove, let FoldContants work its magic later.
return;
}
// Removing an unreachable DO node is messy because it means we still have
// to execute one iteration. If the DO's body has breaks in the middle, it
// can get even more trickier and code size might actually increase.
switch (n.getType()) {
case Token.DO:
case Token.TRY:
case Token.CATCH:
case Token.FINALLY:
return;
}
NodeUtil.redeclareVarsInsideBranch(n);
compiler.reportCodeChange();
if (logger.isLoggable(Level.FINE)) {
logger.fine("Removing " + n.toString());
}
NodeUtil.removeChild(n.getParent(), n);
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.parsing;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.mozilla.rhino.ScriptRuntime;
/**
* This class implements the scanner for JsDoc strings.
*
* It is heavily based on Rhino's TokenStream.
*
*/
class JsDocTokenStream {
/*
* For chars - because we need something out-of-range
* to check. (And checking EOF by exception is annoying.)
* Note distinction from EOF token type!
*/
private final static int
EOF_CHAR = -1;
JsDocTokenStream(String sourceString) {
this(sourceString, 0);
}
JsDocTokenStream(String sourceString, int lineno) {
this(sourceString, lineno, 0);
}
JsDocTokenStream(String sourceString, int lineno, int initCharno) {
Preconditions.checkNotNull(sourceString);
this.lineno = lineno;
this.sourceString = sourceString;
this.sourceEnd = sourceString.length();
this.sourceCursor = this.cursor = 0;
this.initLineno = lineno;
this.initCharno = initCharno;
}
/**
* Tokenizes JSDoc comments.
*/
@SuppressWarnings("fallthrough")
final JsDocToken getJsDocToken() {
int c;
stringBufferTop = 0;
for (;;) {
// eat white spaces
for (;;) {
charno = -1;
c = getChar();
if (c == EOF_CHAR) {
return JsDocToken.EOF;
} else if (c == '\n') {
return JsDocToken.EOL;
} else if (!isJSSpace(c)) {
break;
}
}
switch (c) {
// annotation, e.g. @type or @constructor
case '@':
do {
c = getChar();
if (isAlpha(c)) {
addToString(c);
} else {
ungetChar(c);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return JsDocToken.ANNOTATION;
}
} while (true);
case '*':
if (matchChar('/')) {
return JsDocToken.EOC;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
ungetChar(c);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return this.string;
default:
addToString(c);
break;
}
}
}
final int getLineno() { return lineno; }
final int getCharno() {
return lineno == initLineno? initCharno + charno : charno;
}
final String getString() { return string; }
final boolean eof() { return hitEOF; }
private String getStringFromBuffer() {
tokenEnd = cursor;
return new String(stringBuffer, 0, stringBufferTop);
}
private void addToString(int c) {
int N = stringBufferTop;
if (N == stringBuffer.length) {
char[] tmp = new char[stringBuffer.length * 2];
System.arraycopy(stringBuffer, 0, tmp, 0, N);
stringBuffer = tmp;
}
stringBuffer[N] = (char)c;
stringBufferTop = N + 1;
}
private void ungetChar(int c) {
// can not unread past across line boundary
assert(!(ungetCursor != 0 && ungetBuffer[ungetCursor - 1] == '\n'));
ungetBuffer[ungetCursor++] = c;
cursor--;
}
private boolean matchChar(int test) {
int c = getCharIgnoreLineEnd();
if (c == test) {
tokenEnd = cursor;
return true;
} else {
ungetCharIgnoreLineEnd(c);
return false;
}
}
private static boolean isAlpha(int c) {
// Use 'Z' < 'a'
if (c <= 'Z') {
return 'A' <= c;
} else {
return 'a' <= c && c <= 'z';
}
}
private boolean isJSDocString(int c) {
switch (c) {
case '@':
case '*':
case ',':
case '>':
case ':':
case '(':
case ')':
case '{':
case '}':
case '[':
case ']':
case '?':
case '!':
case '|':
case '=':
case EOF_CHAR:
case '\n':
return false;
default:
return !isJSSpace(c);
}
}
/* As defined in ECMA. jsscan.c uses C isspace() (which allows
* \v, I think.) note that code in getChar() implicitly accepts
* '\r' == \u000D as well.
*/
static boolean isJSSpace(int c) {
if (c <= 127) {
return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB;
} else {
return c == 0xA0
|| Character.getType((char)c) == Character.SPACE_SEPARATOR;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
private static boolean isJSFormatChar(int c) {
return c > 127 && Character.getType((char)c) == Character.FORMAT;
}
/**
* Allows the JSDocParser to update the character offset
* so that getCharno() returns a valid character position.
*/
void update() {
charno = getOffset();
}
private int peekChar() {
int c = getChar();
ungetChar(c);
return c;
}
protected int getChar() {
if (ungetCursor != 0) {
cursor++;
--ungetCursor;
if (charno == -1) {
charno = getOffset();
}
return ungetBuffer[ungetCursor];
}
for(;;) {
int c;
if (sourceCursor == sourceEnd) {
hitEOF = true;
if (charno == -1) {
charno = getOffset();
}
return EOF_CHAR;
}
cursor++;
c = sourceString.charAt(sourceCursor++);
if (lineEndChar >= 0) {
if (lineEndChar == '\r' && c == '\n') {
lineEndChar = '\n';
continue;
}
lineEndChar = -1;
lineStart = sourceCursor - 1;
lineno++;
}
if (c <= 127) {
if (c == '\n' || c == '\r') {
lineEndChar = c;
c = '\n';
}
} else {
if (isJSFormatChar(c)) {
continue;
}
if (ScriptRuntime.isJSLineTerminator(c)) {
lineEndChar = c;
c = '\n';
}
}
if (charno == -1) {
charno = getOffset();
}
return c;
}
}
private int getCharIgnoreLineEnd() {
if (ungetCursor != 0) {
cursor++;
--ungetCursor;
if (charno == -1) {
charno = getOffset();
}
return ungetBuffer[ungetCursor];
}
for(;;) {
int c;
if (sourceCursor == sourceEnd) {
hitEOF = true;
if (charno == -1) {
charno = getOffset();
}
return EOF_CHAR;
}
cursor++;
c = sourceString.charAt(sourceCursor++);
if (c <= 127) {
if (c == '\n' || c == '\r') {
lineEndChar = c;
c = '\n';
}
} else {
if (isJSFormatChar(c)) {
continue;
}
if (ScriptRuntime.isJSLineTerminator(c)) {
lineEndChar = c;
c = '\n';
}
}
if (charno == -1) {
charno = getOffset();
}
return c;
}
}
private void unget
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* An abstract class whose implementations run peephole optimizations:
* optimizations that look at a small section of code and either remove
* that code (if it is not needed) or replaces it with smaller code.
*
*/
abstract class AbstractPeepholeOptimization {
private NodeTraversal currentTraversal;
/**
* Given a node to optimize and a traversal, optimize the node. Subclasses
* should override to provide their own peephole optimization.
*
* @param subtree The subtree that will be optimized.
* @return The new version of the subtree (or null if the subtree or one of
* its parents was removed from the AST). If the subtree has not changed,
* this method must return {@code subtree}.
*/
abstract Node optimizeSubtree(Node subtree);
/**
* Helper method for reporting an error to the compiler when applying a
* peephole optimization.
*
* @param diagnostic The error type
* @param n The node for which the error should be reported
*/
protected void error(DiagnosticType diagnostic, Node n) {
JSError error = currentTraversal.makeError(n, diagnostic, n.toString());
currentTraversal.getCompiler().report(error);
}
/**
* Helper method for telling the compiler that something has changed.
* Subclasses must call these if they have changed the AST.
*/
protected void reportCodeChange() {
Preconditions.checkNotNull(currentTraversal);
currentTraversal.getCompiler().reportCodeChange();
}
/**
* Are the nodes equal for the purpose of inlining?
* If type aware optimizations are on, type equality is checked.
*/
protected boolean areNodesEqualForInlining(Node n1, Node n2) {
/* Our implementation delegates to the compiler. We provide this
* method because we don't want to expose Compiler to PeepholeOptimizations.
*/
Preconditions.checkNotNull(currentTraversal);
return currentTraversal.getCompiler().areNodesEqualForInlining(n1, n2);
}
/**
* Is the current AST normalized? (e.g. has
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> the Normalize pass been run
* and has the Denormalize pass not yet been run?)
*/
protected boolean isASTNormalized() {
Preconditions.checkNotNull(currentTraversal);
Preconditions.checkNotNull(currentTraversal.getCompiler());
return currentTraversal.getCompiler().isNormalized();
}
/**
* Informs the optimization that a traversal will begin.
*/
void beginTraversal(NodeTraversal traversal) {
currentTraversal = traversal;
}
/**
* Informs the optimization that a traversal has completed.
*/
void endTraversal(NodeTraversal traversal) {
currentTraversal = null;
}
// NodeUtil's mayEffectMutableState and mayHaveSideEffects need access to the
// compiler object, route them through here to give them access.
/**
* @return Whether the node may create new mutable state, or change existing
* state.
*/
boolean mayEffectMutableState(Node n) {
return NodeUtil.mayEffectMutableState(n, currentTraversal.getCompiler());
}
/**
* @return Whether the node may have side effects when executed.
*/
boolean mayHaveSideEffects(Node n) {
return NodeUtil.mayHaveSideEffects(n, currentTraversal.getCompiler());
}
/**
* Check if the specified node is null or is still in the AST.
*/
@VisibleForTesting
static Node validateResult(Node n) {
done: {
if (n != null && n.getType() != Token.SCRIPT
&& (n.getType() != Token.BLOCK || !n.isSyntheticBlock())) {
for (Node parent : n.getAncestors()) {
if (parent.getType() == Token.SCRIPT) {
break done;
}
}
Preconditions.checkState(false);
}
}
return n;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import java.util.List;
/**
* Defines a way join a list of LatticeElements.
*/
interface JoinOp<L extends LatticeElement> extends Function<List<L>, L> {
/**
* An implementation of {@code JoinOp} that makes it easy to join to
* lattice elements at a time.
*/
static abstract class BinaryJoinOp<L extends LatticeElement>
implements JoinOp<L> {
@Override
public final L apply(List<L> values) {
Preconditions.checkArgument(!values.isEmpty());
int size = values.size();
if (size == 1) {
return values.get(0);
} else if (size == 2) {
return apply(values.get(0), values.get(1));
} else {
int mid = computeMidPoint(size);
return apply(
apply(values.subList(0, mid)),
apply(values.subList(mid, size)));
}
}
/**
* Creates a new lattice that will be the join of two input lattices.
*
* @return The join of {@code latticeA} and {@code latticeB}.
*/
abstract L apply(L latticeA, L latticeB);
/**
* Finds the midpoint of a list. The function will favor two lists of
* even length instead of two lists of the same odd length. The list
* must be at least length two.
*
* @param size Size of the list.
*/
static int computeMidPoint(int size) {
int midpoint = size >>> 1;
if (size > 4) {
/* Any list longer than 4 should prefer an even split point
* over the true midpoint, so that [0,6] splits at 2, not 3. */
midpoint &= -2; // (0xfffffffe) clears low bit so midpoint is even
}
return midpoint;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.regex.Pattern;
/**
* A peephole optimization that minimizes code by simplifying conditional
* expressions, replacing IFs with HOOKs, replacing object constructors
* with literals, and simplifying returns.
*
*/
public class PeepholeSubstituteAlternateSyntax
extends AbstractPeepholeOptimization {
private static final int AND_PRECEDENCE = NodeUtil.precedence(Token.AND);
private static final int OR_PRECEDENCE = NodeUtil.precedence(Token.OR);
static final DiagnosticType INVALID_REGULAR_EXPRESSION_FLAGS =
DiagnosticType.error(
"JSC_INVALID_REGULAR_EXPRESSION_FLAGS",
"Invalid flags to RegExp constructor: {0}");
static final Predicate<Node> DONT_TRAVERSE_FUNCTIONS_PREDICATE
= new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return input.getType() != Token.FUNCTION;
}
};
/**
* Tries apply our various peephole minimizations on the passed in node.
*/
@Override
@SuppressWarnings("fallthrough")
public Node optimizeSubtree(Node node) {
switch(node.getType()) {
case Token.RETURN:
return tryReduceReturn(node);
case Token.NOT:
tryMinimizeCondition(node.getFirstChild());
return tryMinimizeNot(node);
case Token.IF:
tryMinimizeCondition(node.getFirstChild());
return tryMinimizeIf(node);
case Token.EXPR_RESULT:
tryMinimizeCondition(node.getFirstChild());
return node;
case Token.HOOK:
tryMinimizeCondition(node.getFirstChild());
return node;
case Token.WHILE:
case Token.DO:
tryMinimizeCondition(NodeUtil.getConditionExpression(node));
return node;
case Token.FOR:
if (!NodeUtil.isForIn(node
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>)) {
tryMinimizeCondition(NodeUtil.getConditionExpression(node));
}
return node;
case Token.NEW:
node = tryFoldStandardConstructors(node);
if (node.getType() != Token.CALL) {
return node;
}
// Fall through on purpose because tryFoldStandardConstructors() may
// convert a NEW node into a CALL node
case Token.CALL:
return tryFoldLiteralConstructor(node);
default:
return node; //Nothing changed
}
}
/**
* Reduce "return undefined" or "return void 0" to simply "return".
*
* Returns the replacement for n, or the original if no change was made.
*/
private Node tryReduceReturn(Node n) {
Node result = n.getFirstChild();
boolean possibleException = result != null &&
ControlFlowAnalysis.mayThrowException(result);
// Try to use a substitute that with a break because it is shorter.
// First lets pretend it is a break with no labels.
Node breakTarget = n;
boolean safe = true;
for (;!ControlFlowAnalysis.isBreakTarget(breakTarget, null /* no label */);
breakTarget = breakTarget.getParent()) {
if (NodeUtil.isFunction(breakTarget) ||
breakTarget.getType() == Token.SCRIPT) {
// We can switch the return to a break if the return value has
// side effect and it must encounter a finally.
// example: return alert('a') -> finally { alert('b') } ->
// return alert('a')
// prints a then b. If the first return is a break,
// it prints b then a.
safe = false;
break;
}
}
Node follow = ControlFlowAnalysis.computeFollowNode(breakTarget);
// Skip pass all the finally blocks because both the break and return will
// also trigger all the finally blocks. However, the order of execution is
// slightly changed. Consider:
//
// return a() -> finally { b() } -> return a()
//
// which would call a() first. However, changing the first return to a
// break will result in calling b().
while (follow != null &&
NodeUtil.isTryFinallyNode(follow.getParent(), follow)) {
if (result != null &&
// TODO(user): Use the new side effects API for more accuracy.
(NodeUtil.canBeSideEffected(result) ||
NodeUtil.mayHaveSideEffects(result))) {
safe = false;
break;
}
follow = ControlFlowAnalysis.computeFollowNode(follow);
}
if (safe) {
if (follow == null) {
// When follow is null, this mean the follow of a break target is the
// end of a function. This means a break is same as return.
if (result == null) {
n.setType(Token.BREAK);
reportCodeChange();
return n;
}
} else if (follow.getType() == Token.RETURN &&
(result == follow.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>getFirstChild() ||
(result != null && follow.hasChildren() &&
result.checkTreeEqualsSilent(follow.getFirstChild())) &&
ControlFlowAnalysis.getExceptionHandler(n) ==
ControlFlowAnalysis.getExceptionHandler(follow)
)) {
// When the follow is a return, if both doesn't return anything
// or both returns the same thing. This mean we can replace it with a
// break.
n.removeChildren();
n.setType(Token.BREAK);
reportCodeChange();
return n;
}
// If any of the above is executed, we must return because n is no longer
// a "return" node.
}
// TODO(user): consider cases such as if (x) { return 1} return 1;
if (result != null) {
switch (result.getType()) {
case Token.VOID:
Node operand = result.getFirstChild();
if (!mayHaveSideEffects(operand)) {
n.removeFirstChild();
reportCodeChange();
}
break;
case Token.NAME:
String name = result.getString();
if (name.equals("undefined")) {
n.removeFirstChild();
reportCodeChange();
}
break;
default:
//Do nothing
break;
}
}
return n;
}
/**
* Try to minimize NOT nodes such as !(x==y).
*
* Returns the replacement for n or the original if no change was made
*/
private Node tryMinimizeNot(Node n) {
Node parent = n.getParent();
Node notChild = n.getFirstChild();
// negative operator of the current one : == -> != for instance.
int complementOperator;
switch (notChild.getType()) {
case Token.EQ:
complementOperator = Token.NE;
break;
case Token.NE:
complementOperator = Token.EQ;
break;
case Token.SHEQ:
complementOperator = Token.SHNE;
break;
case Token.SHNE:
complementOperator = Token.SHEQ;
break;
// GT, GE, LT, LE are not handled in this because !(x<NaN) != x>=NaN.
default:
return n;
}
Node newOperator = n.removeFirstChild();
newOperator.setType(complementOperator);
parent.replaceChild(n, newOperator);
reportCodeChange();
return newOperator;
}
/**
* Try turning IF nodes into smaller HOOKs
*
* Returns the replacement for n or the original if no replacement was
* necessary.
*/
private Node tryMinimizeIf(Node n) {
Node parent = n.getParent();
Node cond = n.getFirstChild();
/* If the condition is a literal, we'll let other
* optimizations try to remove useless code.
*/
if (NodeUtil.isLiteralValue(cond, true)) {
return n;
}
Node thenBranch = cond.getNext();
Node elseBranch = thenBranch.getNext();
if (elseBranch == null) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> if (isExpressBlock(thenBranch)) {
Node expr = getBlockExpression(thenBranch);
if (isPropertyAssignmentInExpression(expr)) {
// Keep opportunities for CollapseProperties such as
// a.longIdentifier || a.longIdentifier = ... -> var a = ...;
return n;
}
if (cond.getType() == Token.NOT) {
// if(!x)bar(); -> x||bar();
if (isLowerPrecedenceInExpression(cond, OR_PRECEDENCE) &&
isLowerPrecedenceInExpression(expr.getFirstChild(),
OR_PRECEDENCE)) {
// It's not okay to add two sets of parentheses.
return n;
}
Node or = new Node(Token.OR, cond.removeFirstChild(),
expr.removeFirstChild()).copyInformationFrom(n);
Node newExpr = NodeUtil.newExpr(or);
parent.replaceChild(n, newExpr);
reportCodeChange();
return newExpr;
}
// if(x)foo(); -> x&&foo();
if (isLowerPrecedenceInExpression(cond, AND_PRECEDENCE) ||
isLowerPrecedenceInExpression(expr.getFirstChild(),
AND_PRECEDENCE)) {
// One additional set of parentheses isn't worth it.
return n;
}
n.removeChild(cond);
Node and = new Node(Token.AND, cond, expr.removeFirstChild())
.copyInformationFrom(n);
Node newExpr = NodeUtil.newExpr(and);
parent.replaceChild(n, newExpr);
reportCodeChange();
return newExpr;
}
return n;
}
/* TODO(dcc) This modifies the siblings of n, which is undesirable for a
* peephole optimization. This should probably get moved to another pass.
*/
tryRemoveRepeatedStatements(n);
// if(!x)foo();else bar(); -> if(x)bar();else foo();
// An additional set of curly braces isn't worth it.
if (cond.getType() == Token.NOT && !consumesDanglingElse(elseBranch)) {
n.replaceChild(cond, cond.removeFirstChild());
n.removeChild(thenBranch);
n.addChildToBack(thenBranch);
reportCodeChange();
return n;
}
// if(x)return 1;else return 2; -> return x?1:2;
if (isReturnExpressBlock(thenBranch) && isReturnExpressBlock(elseBranch)) {
Node thenExpr = getBlockReturnExpression(thenBranch);
Node elseExpr = getBlockReturnExpression(elseBranch);
n.removeChild(cond);
thenExpr.detachFromParent();
elseExpr.detachFromParent();
// note - we ignore any cases with "return;", technically this
// can be converted to "return undefined;" or some variant, but
// that does not help code size.
Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr)
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
.copyInformationFrom(n);
Node returnNode = new Node(Token.RETURN, hookNode);
parent.replaceChild(n, returnNode);
reportCodeChange();
return returnNode;
}
boolean thenBranchIsExpressionBlock = isExpressBlock(thenBranch);
boolean elseBranchIsExpressionBlock = isExpressBlock(elseBranch);
if (thenBranchIsExpressionBlock && elseBranchIsExpressionBlock) {
Node thenOp = getBlockExpression(thenBranch).getFirstChild();
Node elseOp = getBlockExpression(elseBranch).getFirstChild();
if (thenOp.getType() == elseOp.getType()) {
// if(x)a=1;else a=2; -> a=x?1:2;
if (NodeUtil.isAssignmentOp(thenOp)) {
Node lhs = thenOp.getFirstChild();
if (areNodesEqualForInlining(lhs, elseOp.getFirstChild()) &&
// if LHS has side effects, don't proceed [since the optimization
// evaluates LHS before cond]
// NOTE - there are some circumstances where we can
// proceed even if there are side effects...
!mayEffectMutableState(lhs)) {
n.removeChild(cond);
Node assignName = thenOp.removeFirstChild();
Node thenExpr = thenOp.removeFirstChild();
Node elseExpr = elseOp.getLastChild();
elseOp.removeChild(elseExpr);
Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr)
.copyInformationFrom(n);
Node assign = new Node(thenOp.getType(), assignName, hookNode)
.copyInformationFrom(thenOp);
Node expr = NodeUtil.newExpr(assign);
parent.replaceChild(n, expr);
reportCodeChange();
return expr;
}
} else if (NodeUtil.isCall(thenOp)) {
// if(x)foo();else bar(); -> x?foo():bar()
n.removeChild(cond);
thenOp.detachFromParent();
elseOp.detachFromParent();
Node hookNode = new Node(Token.HOOK, cond, thenOp, elseOp)
.copyInformationFrom(n);
Node expr = NodeUtil.newExpr(hookNode);
parent.replaceChild(n, expr);
reportCodeChange();
return expr;
}
}
return n;
}
boolean thenBranchIsVar = isVarBlock(thenBranch);
boolean elseBranchIsVar = isVarBlock(elseBranch);
// if(x)var y=1;else y=2 -> var y=x?1:2
if (thenBranchIsVar && elseBranchIsExpressionBlock &&
NodeUtil.isAssign(getBlockExpression(elseBranch).getFirstChild())) {
Node var = getBlockVar(thenBranch);
Node elseAssign = getBlockExpression(elseBranch).getFirstChild();
Node name1 = var.getFirstChild();
Node maybeName2 = elseAssign.getFirstChild();
if (name1.hasChildren()
&& maybeName
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>2.getType() == Token.NAME
&& name1.getString().equals(maybeName2.getString())) {
Node thenExpr = name1.removeChildren();
Node elseExpr = elseAssign.getLastChild().detachFromParent();
cond.detachFromParent();
Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr)
.copyInformationFrom(n);
var.detachFromParent();
name1.addChildrenToBack(hookNode);
parent.replaceChild(n, var);
reportCodeChange();
return var;
}
// if(x)y=1;else var y=2 -> var y=x?1:2
} else if (elseBranchIsVar && thenBranchIsExpressionBlock &&
NodeUtil.isAssign(getBlockExpression(thenBranch).getFirstChild())) {
Node var = getBlockVar(elseBranch);
Node thenAssign = getBlockExpression(thenBranch).getFirstChild();
Node maybeName1 = thenAssign.getFirstChild();
Node name2 = var.getFirstChild();
if (name2.hasChildren()
&& maybeName1.getType() == Token.NAME
&& maybeName1.getString().equals(name2.getString())) {
Node thenExpr = thenAssign.getLastChild().detachFromParent();
Node elseExpr = name2.removeChildren();
cond.detachFromParent();
Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr)
.copyInformationFrom(n);
var.detachFromParent();
name2.addChildrenToBack(hookNode);
parent.replaceChild(n, var);
reportCodeChange();
return var;
}
}
return n;
}
/**
* Try to remove duplicate statements from IF blocks. For example:
*
* if (a) {
* x = 1;
* return true;
* } else {
* x = 2;
* return true;
* }
*
* becomes:
*
* if (a) {
* x = 1;
* } else {
* x = 2;
* }
* return true;
*
* @param n The IF node to examine.
*/
private void tryRemoveRepeatedStatements(Node n) {
Preconditions.checkState(n.getType() == Token.IF);
Node parent = n.getParent();
if (!NodeUtil.isStatementBlock(parent)) {
// If the immediate parent is something like a label, we
// can't move the statement, so bail.
return;
}
Node cond = n.getFirstChild();
Node trueBranch = cond.getNext();
Node falseBranch = trueBranch.getNext();
Preconditions.checkNotNull(trueBranch);
Preconditions.checkNotNull(falseBranch);
while (true) {
Node lastTrue = trueBranch.getLastChild();
Node lastFalse = falseBranch.getLastChild();
if (lastTrue == null || lastFalse == null
|| !areNodesEqualForInlining(last
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>True, lastFalse)) {
break;
}
lastTrue.detachFromParent();
lastFalse.detachFromParent();
parent.addChildAfter(lastTrue, n);
reportCodeChange();
}
}
/**
* @return Whether the node is a block with a single statement that is
* an expression.
*/
private boolean isExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
return NodeUtil.isExpressionNode(n.getFirstChild());
}
}
return false;
}
/**
* @return The expression node.
*/
private Node getBlockExpression(Node n) {
Preconditions.checkState(isExpressBlock(n));
return n.getFirstChild();
}
/**
* @return Whether the node is a block with a single statement that is
* an return.
*/
private boolean isReturnExpressBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node first = n.getFirstChild();
if (first.getType() == Token.RETURN) {
return first.hasOneChild();
}
}
}
return false;
}
/**
* @return The expression that is part of the return.
*/
private Node getBlockReturnExpression(Node n) {
Preconditions.checkState(isReturnExpressBlock(n));
return n.getFirstChild().getFirstChild();
}
/**
* @return Whether the node is a block with a single statement that is
* a VAR declaration of a single variable.
*/
private boolean isVarBlock(Node n) {
if (n.getType() == Token.BLOCK) {
if (n.hasOneChild()) {
Node first = n.getFirstChild();
if (first.getType() == Token.VAR) {
return first.hasOneChild();
}
}
}
return false;
}
/**
* @return The var node.
*/
private Node getBlockVar(Node n) {
Preconditions.checkState(isVarBlock(n));
return n.getFirstChild();
}
/**
* Does a statement consume a 'dangling else'? A statement consumes
* a 'dangling else' if an 'else' token following the statement
* would be considered by the parser to be part of the statement.
*/
private boolean consumesDanglingElse(Node n) {
while (true) {
switch (n.getType()) {
case Token.IF:
if (n.getChildCount() < 3) {
return true;
}
// This IF node has no else clause.
n = n.getLastChild();
continue;
case Token.WITH:
case Token.WHILE:
case Token.FOR:
n = n.getLastChild();
continue;
default:
return false;
}
}
}
/**
* Does the expression contain an operator with lower precedence than
* the argument?
*/
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> private boolean isLowerPrecedenceInExpression(Node n,
final int precedence) {
Predicate<Node> isLowerPrecedencePredicate = new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return NodeUtil.precedence(input.getType()) < precedence;
}
};
return NodeUtil.has(n, isLowerPrecedencePredicate,
DONT_TRAVERSE_FUNCTIONS_PREDICATE);
}
/**
* Does the expression contain a property assignment?
*/
private boolean isPropertyAssignmentInExpression(Node n) {
Predicate<Node> isPropertyAssignmentInExpressionPredicate =
new Predicate<Node>() {
@Override
public boolean apply(Node input) {
return (input.getType() == Token.GETPROP &&
input.getParent().getType() == Token.ASSIGN);
}
};
return NodeUtil.has(n, isPropertyAssignmentInExpressionPredicate,
DONT_TRAVERSE_FUNCTIONS_PREDICATE);
}
/**
* Try to minimize conditions expressions, as there are additional
* assumptions that can be made when it is known that the final result
* is a boolean.
*
* The following transformations are done recursively:
* !(x||y) --> !x&&!y
* !(x&&y) --> !x||!y
* !!x --> x
* Thus:
* !(x&&!y) --> !x||!!y --> !x||y
*
* Returns the replacement for n, or the original if no change was made
*/
private Node tryMinimizeCondition(Node n) {
Node parent = n.getParent();
switch (n.getType()) {
case Token.NOT:
Node first = n.getFirstChild();
switch (first.getType()) {
case Token.NOT: {
Node newRoot = first.removeFirstChild();
parent.replaceChild(n, newRoot);
reportCodeChange();
// No need to traverse, tryMinimizeCondition is called on the
// NOT children are handled below.
return newRoot;
}
case Token.AND:
case Token.OR: {
Node leftParent = first.getFirstChild();
Node rightParent = first.getLastChild();
if (leftParent.getType() == Token.NOT
&& rightParent.getType() == Token.NOT) {
Node left = leftParent.removeFirstChild();
Node right = rightParent.removeFirstChild();
int newOp = (first.getType() == Token.AND) ? Token.OR : Token.AND;
Node newRoot = new Node(newOp, left, right);
parent.replaceChild(n, newRoot);
reportCodeChange();
// No need to traverse, tryMinimizeCondition is called on the
// AND and OR children below.
return newRoot;
}
}
break;
}
// No need to traverse, tryMinimizeCondition is called on the NOT
// children in the general case in the main post-order traversal.
return n;
case Token.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>OR:
case Token.AND: {
Node left = n.getFirstChild();
Node right = n.getLastChild();
// Because the expression is in a boolean context minimize
// the children, this can't be done in the general case.
left = tryMinimizeCondition(left);
right = tryMinimizeCondition(right);
// Remove useless conditionals
// Handle four cases:
// x || false --> x
// x || true --> true
// x && true --> x
// x && false --> false
TernaryValue rightVal = NodeUtil.getBooleanValue(right);
if (NodeUtil.getBooleanValue(right) != TernaryValue.UNKNOWN) {
int type = n.getType();
Node replacement = null;
boolean rval = rightVal.toBoolean(true);
// (x || FALSE) => x
// (x && TRUE) => x
if (type == Token.OR && !rval ||
type == Token.AND && rval) {
replacement = left;
} else if (!mayHaveSideEffects(left)) {
replacement = right;
}
if (replacement != null) {
n.detachChildren();
parent.replaceChild(n, replacement);
reportCodeChange();
return replacement;
}
}
return n;
}
case Token.HOOK: {
Node condition = n.getFirstChild();
Node trueNode = n.getFirstChild().getNext();
Node falseNode = n.getLastChild();
// Because the expression is in a boolean context minimize
// the result children, this can't be done in the general case.
// The condition is handled in the general case in #optimizeSubtree
trueNode = tryMinimizeCondition(trueNode);
falseNode = tryMinimizeCondition(falseNode);
// Handle four cases:
// x ? true : false --> x
// x ? false : true --> !x
// x ? true : y --> x || y
// x ? y : false --> x && y
Node replacement = null;
if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.TRUE
&& NodeUtil.getBooleanValue(falseNode) == TernaryValue.FALSE) {
// Remove useless conditionals, keep the condition
condition.detachFromParent();
replacement = condition;
} else if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.FALSE
&& NodeUtil.getBooleanValue(falseNode) == TernaryValue.TRUE) {
// Remove useless conditionals, keep the condition
condition.detachFromParent();
replacement = new Node(Token.NOT, condition);
} else if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.TRUE) {
// Remove useless true case.
n.detachChildren();
replacement = new Node(Token.OR, condition, falseNode);
} else if (NodeUtil.getBooleanValue(falseNode) == TernaryValue.FALSE) {
// Remove useless false case
n.detachChildren();
replacement = new Node(Token
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.AND, condition, trueNode);
}
if (replacement != null) {
parent.replaceChild(n, replacement);
n = replacement;
reportCodeChange();
}
return n;
}
default:
// while(true) --> while(1)
TernaryValue nVal = NodeUtil.getBooleanValue(n);
if (nVal != TernaryValue.UNKNOWN) {
boolean result = nVal.toBoolean(true);
int equivalentResult = result ? 1 : 0;
return maybeReplaceChildWithNumber(n, parent, equivalentResult);
}
// We can't do anything else currently.
return n;
}
}
/**
* Replaces a node with a number node if the new number node is not equivalent
* to the current node.
*
* Returns the replacement for n if it was replaced, otherwise returns n.
*/
private Node maybeReplaceChildWithNumber(Node n, Node parent, int num) {
Node newNode = Node.newNumber(num);
if (!newNode.isEquivalentTo(n)) {
parent.replaceChild(n, newNode);
reportCodeChange();
return newNode;
}
return n;
}
private static final ImmutableSet<String> STANDARD_OBJECT_CONSTRUCTORS =
// String, Number, and Boolean functions return non-object types, whereas
// new String, new Number, and new Boolean return object types, so don't
// include them here.
ImmutableSet.of(
"Object",
"Array",
"RegExp",
"Error"
);
/**
* Fold "new Object()" to "Object()".
*/
private Node tryFoldStandardConstructors(Node n) {
Preconditions.checkState(n.getType() == Token.NEW);
// If name normalization has been run then we know that
// new Object() does in fact refer to what we think it is
// and not some custom-defined Object().
if (isASTNormalized()) {
if (n.getFirstChild().getType() == Token.NAME) {
String className = n.getFirstChild().getString();
if (STANDARD_OBJECT_CONSTRUCTORS.contains(className)) {
n.setType(Token.CALL);
reportCodeChange();
}
}
}
return n;
}
/**
* Replaces a new Array or Object node with an object literal, unless the
* call to Array or Object is to a local function with the same name.
*/
private Node tryFoldLiteralConstructor(Node n) {
Preconditions.checkArgument(n.getType() == Token.CALL
|| n.getType() == Token.NEW);
Node constructorNameNode = n.getFirstChild();
Node newLiteralNode = null;
// We require the AST to be normalized to ensure that, say,
// Object() really refers to the built-in Object constructor
// and not a user-defined constructor with the same name.
if (isASTNormalized() && Token.NAME == constructorNameNode.getType()) {
String className = constructorNameNode.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>getString();
if ("RegExp".equals(className)) {
// "RegExp("boo", "g")" --> /boo/g
return tryFoldRegularExpressionConstructor(n);
} else {
boolean constructorHasArgs = constructorNameNode.getNext() != null;
if ("Object".equals(className) && !constructorHasArgs) {
// "Object()" --> "{}"
newLiteralNode = new Node(Token.OBJECTLIT);
} else if ("Array".equals(className)) {
// "Array(arg0, arg1, ...)" --> "[arg0, arg1, ...]"
Node arg0 = constructorNameNode.getNext();
FoldArrayAction action = isSafeToFoldArrayConstructor(arg0);
if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS ||
action == FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS) {
newLiteralNode = new Node(Token.ARRAYLIT);
n.removeChildren();
if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS) {
newLiteralNode.addChildrenToFront(arg0);
}
}
}
if (newLiteralNode != null) {
n.getParent().replaceChild(n, newLiteralNode);
reportCodeChange();
return newLiteralNode;
}
}
}
return n;
}
private static enum FoldArrayAction {
NOT_SAFE_TO_FOLD, SAFE_TO_FOLD_WITH_ARGS, SAFE_TO_FOLD_WITHOUT_ARGS}
/**
* Checks if it is safe to fold Array() constructor into []. It can be
* obviously done, if the initial constructor has either no arguments or
* at least two. The remaining case may be unsafe since Array(number)
* actually reserves memory for an empty array which contains number elements.
*/
private FoldArrayAction isSafeToFoldArrayConstructor(Node arg) {
FoldArrayAction action = FoldArrayAction.NOT_SAFE_TO_FOLD;
if (arg == null) {
action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS;
} else if (arg.getNext() != null) {
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
} else {
switch (arg.getType()) {
case (Token.STRING):
// "Array('a')" --> "['a']"
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
break;
case (Token.NUMBER):
// "Array(0)" --> "[]"
if (arg.getDouble() == 0) {
action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS;
}
break;
case (Token.ARRAYLIT):
// "Array([args])" --> "[[args]]"
action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS;
break;
default:
}
}
return action;
}
private Node tryFoldRegularExpressionConstructor(Node n) {
Node parent =
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> n.getParent();
Node constructor = n.getFirstChild();
Node pattern = constructor.getNext(); // e.g. ^foobar$
Node flags = null != pattern ? pattern.getNext() : null; // e.g. gi
// Only run on normalized AST to make sure RegExp() is actually
// the RegExp we expect (if the AST has been normalized then
// other RegExp's will have been renamed to something like RegExp$1)
if (!isASTNormalized()) {
return n;
}
if (null == pattern || (null != flags && null != flags.getNext())) {
// too few or too many arguments
return n;
}
if (// is pattern folded
pattern.getType() == Token.STRING
// make sure empty pattern doesn't fold to //
&& !"".equals(pattern.getString())
// NOTE(nicksantos): Make sure that the regexp isn't longer than
// 100 chars, or it blows up the regexp parser in Opera 9.2.
&& pattern.getString().length() < 100
&& (null == flags || flags.getType() == Token.STRING)
// don't escape patterns with unicode escapes since Safari behaves badly
// (read can't parse or crashes) on regex literals with unicode escapes
&& !containsUnicodeEscape(pattern.getString())) {
// Make sure that / is escaped, so that it will fit safely in /brackets/.
// pattern is a string value with \\ and similar already escaped
pattern = makeForwardSlashBracketSafe(pattern);
Node regexLiteral;
if (null == flags || "".equals(flags.getString())) {
// fold to /foobar/
regexLiteral = new Node(Token.REGEXP, pattern);
} else {
// fold to /foobar/gi
if (!areValidRegexpFlags(flags.getString())) {
error(INVALID_REGULAR_EXPRESSION_FLAGS, flags);
return n;
}
if (!areSafeFlagsToFold(flags.getString())) {
return n;
}
n.removeChild(flags);
regexLiteral = new Node(Token.REGEXP, pattern, flags);
}
parent.replaceChild(n, regexLiteral);
reportCodeChange();
return regexLiteral;
}
return n;
}
private static final Pattern REGEXP_FLAGS_RE = Pattern.compile("^[gmi]*$");
/**
* are the given flags valid regular expression flags?
* Javascript recognizes several suffix flags for regular expressions,
* 'g' - global replace, 'i' - case insensitive, 'm' - multi-line.
* They are case insensitive, and javascript does not recognize the extended
* syntax mode, single-line mode, or expression replacement mode from perl5.
*/
private static boolean areValidRegexpFlags(String flags) {
return REGEXP_FLAGS_RE.matcher(flags).matches();
}
/**
* are the given flags safe to fold?
* We don't fold the regular expression if global ('g') flag is on,
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.graph;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.javascript.jscomp.graph.FixedPointGraphTraversal.EdgeCallback;
/**
* Computes all the reachable nodes. Upon execution of {@link #compute(Object)},
* the graph nodes will be annotated with {@link #REACHABLE} if it is reachable
* from the specified entry node.
*
* @see GraphNode#getAnnotation()
*/
public class GraphReachability<N, E> implements EdgeCallback<N, E> {
// TODO(user): This should work for undirected graphs when
// FixedPointGraphTraversal accepts them.
private final DiGraph<N, E> graph;
private final Predicate<EdgeTuple<N, E>> edgePredicate;
public GraphReachability(DiGraph<N, E> graph) {
this(graph, null);
}
/**
* @param graph The graph.
* @param edgePredicate Given the predecessor P of the a node S and the edge
* coming from P to S, this predicate should return true if S is
* reachable from P using the edge.
*/
public GraphReachability(DiGraph<N, E> graph,
Predicate<EdgeTuple<N, E>> edgePredicate) {
this.graph = graph;
this.edgePredicate = edgePredicate;
}
public void compute(N entry) {
graph.clearNodeAnnotations();
graph.getNode(entry).setAnnotation(REACHABLE);
FixedPointGraphTraversal.newTraversal(this)
.computeFixedPoint(graph, entry);
}
public void recompute(N reachableNode) {
GraphNode<N, E> newReachable = graph.getNode(reachableNode);
Preconditions.checkState(newReachable.getAnnotation() != REACHABLE);
newReachable.setAnnotation(REACHABLE);
FixedPointGraphTraversal.newTraversal(this)
.computeFixedPoint(graph, reachableNode);
}
@Override
public boolean traverseEdge(N source, E e, N destination) {
if (graph.getNode(source).getAnnotation() == REACHABLE &&
(edgePredicate == null ||
edgePredicate.apply(new EdgeTuple<N, E>(source, e, destination)))) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
DiagnosticType.error(
"JSC_INVALID_DEFINE_INIT_ERROR",
"illegal initialization of @define variable {0}");
static final DiagnosticType NON_GLOBAL_DEFINE_INIT_ERROR =
DiagnosticType.error(
"JSC_NON_GLOBAL_DEFINE_INIT_ERROR",
"@define variable {0} assignment must be global");
static final DiagnosticType DEFINE_NOT_ASSIGNABLE_ERROR =
DiagnosticType.error(
"@define variable cannot be assigned here",
"@define variable {0} cannot be assigned due to unsafe code at {1}.");
private static final MessageFormat REASON_DEFINE_NOT_ASSIGNABLE =
new MessageFormat("line {0} of {1}");
/**
* Create a pass that overrides define constants.
*
* TODO(nicksantos): Write a builder to help JSCompiler induce
* {@code replacements} from command-line flags
*
* @param replacements A hash table of names of defines to their replacements.
* All replacements <b>must</b> be literals.
*/
ProcessDefines(AbstractCompiler compiler, Map<String, Node> replacements) {
this.compiler = compiler;
dominantReplacements = replacements;
}
/**
* Injects a pre-computed global namespace, so that the same namespace
* can be re-used for multiple check passes. Returns {@code this} for
* easy chaining.
*/
ProcessDefines injectNamespace(GlobalNamespace namespace) {
this.namespace = namespace;
return this;
}
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, root);
}
overrideDefines(collectDefines(root, namespace));
}
private void overrideDefines(Map<String, DefineInfo> allDefines) {
boolean changed = false;
for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) {
String defineName = def.getKey();
DefineInfo info = def.getValue();
Node inputValue = dominantReplacements.get(defineName);
Node finalValue = inputValue != null ?
inputValue : info.getLastValue();
if (finalValue != info.initialValue) {
info.initialValueParent.replaceChild(
info.initialValue, finalValue.cloneTree());
compiler.addToDebugLog("Overriding @define variable " + defineName);
changed = changed ||
finalValue.getType() != info.initialValue.getType() ||
!finalValue.isEquivalentTo(info.initialValue);
}
}
if (changed) {
compiler.reportCodeChange();
}
Set<String> unusedReplacements = dominantReplacements.keySet();
unusedReplacements.removeAll(allDefines.keySet());
unusedReplacements.removeAll(KNOWN_DEFINES);
for (String unknownDefine : unusedReplacements) {
compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine));
}
}
private static String format(MessageFormat
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> format, Object... params) {
return format.format(params);
}
/**
* Only defines of literal number, string, or boolean are supported.
*/
private boolean isValidDefineType(JSTypeExpression expression) {
JSType type = expression.evaluate(null, compiler.getTypeRegistry());
return !type.isUnknownType() && type.isSubtype(
compiler.getTypeRegistry().getNativeType(
JSTypeNative.NUMBER_STRING_BOOLEAN));
}
/**
* Finds all defines, and creates a {@link DefineInfo} data structure for
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values()) {
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
name.declaration.sourceName,
name.declaration.node,
INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else if (name.refs != null) {
for (Ref ref : name.refs) {
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.getType() == Token.VAR && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign.
private Node lvalueToRemoveLater = null;
// A stack tied to the node traversal, to keep track of whether
// we're in a conditional block. If 1 is at the top, assignment to
// a define is allowed. Otherwise, it's not allowed.
private final Deque<Integer> assignAllowed;
CollectDefines(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>AbstractCompiler compiler, List<Name> listOfDefines) {
this.compiler = compiler;
this.allDefines = Maps.newHashMap();
assignableDefines = Maps.newHashMap();
assignAllowed = new ArrayDeque<Integer>();
assignAllowed.push(1);
// Create a map of references to defines keyed by node for easy lookup
allRefInfo = Maps.newHashMap();
for (Name name : listOfDefines) {
if (name.declaration != null) {
allRefInfo.put(name.declaration.node,
new RefInfo(name.declaration, name));
}
if (name.refs != null) {
for (Ref ref : name.refs) {
// If there's a TWIN def, only put one of the twins in.
if (ref.getTwin() == null || !ref.getTwin().isSet()) {
allRefInfo.put(ref.node, new RefInfo(ref, name));
}
}
}
}
}
/**
* Get a map of {@link DefineInfo} structures, keyed by the name of
* the define.
*/
Map<String, DefineInfo> getAllDefines() {
return allDefines;
}
/**
* Keeps track of whether the traversal is in a conditional branch.
* We traverse all nodes of the parse tree.
*/
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
updateAssignAllowedStack(n, true);
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
RefInfo refInfo = allRefInfo.get(n);
if (refInfo != null) {
Ref ref = refInfo.ref;
Name name = refInfo.name;
String fullName = name.fullName();
switch (ref.type) {
case SET_FROM_GLOBAL:
case SET_FROM_LOCAL:
Node valParent = getValueParent(ref);
Node val = valParent.getLastChild();
if (valParent.getType() == Token.ASSIGN && name.isSimpleName() &&
name.declaration == ref) {
// For defines, it's an error if a simple name is assigned
// before it's declared
compiler.report(
t.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName));
} else if (processDefineAssignment(t, fullName, val, valParent)) {
// remove the assignment so that the variable is still declared,
// but no longer assigned to a value, e.g.,
// DEF_FOO = 5; // becomes "5;"
// We can't remove the ASSIGN/VAR when we're still visiting its
// children, so we'll have to come back later to remove it.
refInfo.name.removeRef(ref);
lvalueToRemoveLater = valParent;
}
break;
default:
if (t.inGlobalScope()) {
// Treat this as a reference to a define in the global scope.
// After this point, the define
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> must not be reassigned,
// or it's an error.
DefineInfo info = assignableDefines.get(fullName);
if (info != null) {
setDefineInfoNotAssignable(info, t);
assignableDefines.remove(fullName);
}
}
break;
}
}
if (!t.inGlobalScope() &&
n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) {
// warn about @define annotations in local scopes
compiler.report(
t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, ""));
}
if (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.getType() == Token.ASSIGN) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.getType() == Token.NAME);
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.getType() == Token.CALL) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, expecially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of value.
* @return Whether we should remove this assignment from the parse tree.
*/
private boolean processDefineAssignment(NodeTraversal t,
String name, Node value, Node valueParent) {
if (value == null || !NodeUtil.isValidDefineValue(value,
allDefines.keySet())) {
compiler.report(
t.makeError(value, INVALID_DEFINE_INIT_ERROR, name));
} else if (!isAssignAllowed()) {
compiler.report(
t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name));
} else {
DefineInfo info = allDefines.get(name);
if (info == null) {
// First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
assignableDefines.put(name, info);
} else if (info.recordAssignment(value)) {
// The define was already initialized, but this is a safe
// re-assignment.
return true;
} else {
// The define was already initialized, and this is an unsafe
// re-assignment.
compiler.report(
t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR,
name, info.getReasonWhyNotAssignable()));
}
}
return false;
}
/**
* Gets the parent node of the value for any assignment to a Name.
* For example, in the assignment
* {@code var x = 3;}
* the parent would be the NAME node.
*/
private static Node getValueParent(Ref ref) {
// there are two types of declarations: VARs and ASSIGNs
return ref.node.getParent() != null &&
ref.node.getParent().getType() == Token.VAR ?
ref.node : ref.node.getParent();
}
/**
* Records the fact that because of the current node in the node traversal,
* the define can't ever be assigned again.
*
* @param info Represents the define variable.
* @param t The current traversal.
*/
private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) {
info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE,
t.getLineNumber(), t.getSourceName()));
}
/**
* A simple data structure for associating a Ref with the name
* that it references.
*/
private static class RefInfo {
final Ref ref;
final Name name;
RefInfo(Ref ref, Name name) {
this.ref = ref;
this.name = name;
}
}
}
/**
* A simple class for storing information about a define.
* Gathers the initial value, the last assigned value, and whether
* the define can be safely assigned a new value.
*/
private static final class DefineInfo {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> traversed, they will be visited by
* {@link #visit(NodeTraversal, Node, Node)} in post order.</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
* @return whether the children of this node should be visited
*/
boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent);
/**
* <p>Visits a node in post order (after its children have been visited).
* A node is visited only if all its parents should be traversed
* ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
*/
void visit(NodeTraversal t, Node n, Node parent);
}
/**
* Callback that also knows about scope changes
*/
public interface ScopedCallback extends Callback {
/**
* Called immediately after entering a new scope. The new scope can
* be accessed through t.getScope()
*/
void enterScope(NodeTraversal t);
/**
* Called immediately before exiting a scope. The ending scope can
* be accessed through t.getScope()
*/
void exitScope(NodeTraversal t);
}
/**
* Abstract callback to visit all nodes in post order.
*/
public abstract static class AbstractPostOrderCallback implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
}
/**
* Abstract callback to visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't
* traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (!sourceName.isEmpty()) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
sourceName = "";
curNode = root;
pushScope(root);
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
if (n == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.isGlobal());
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.getType() == Token.FUNCTION) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild().getNext();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may be not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
/**
* Gets the compiler.
*/
public Compiler getCompiler() {
// TODO(nicksantos): Remove this type cast. This is just temporary
// while refactoring.
return (Compiler) compiler
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >=0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
*/
public CompilerInput getInput() {
return compiler.getInput(sourceName);
}
/**
* Gets the current input module.
*/
public JSModule getModule() {
CompilerInput input = getInput();
return input == null ? null : input.getModule();
}
/** Returns the node currently being traversed. */
public Node getCurrentNode() {
return curNode;
}
/**
* Traverses a node recursively.
*/
public static void traverse(
AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverse(root);
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
@SuppressWarnings("fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getType() == Token.FUNCTION);
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.getType() == Token.BLOCK);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode != null);
scopes.push(s);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to the previous scope (e.g. when leaving a function). */
private void popScope() {
if (scopeCallback != null) {
scopeCallback.exitScope(this);
}
if (scopeRoots.isEmpty()) {
scopes.pop();
} else {
scopeRoots.pop();
}
cfgs.pop();
}
/** Gets the current scope. */
public Scope getScope() {
Scope scope = scopes.isEmpty() ? null : scopes.peek();
if (scopeRoots.isEmpty()) {
return scope;
}
Iterator<Node> it = scopeRoots.descendingIterator();
while (it.hasNext()) {
scope = scopeCreator.createScope(it.next(), scope);
scopes.push(scope);
}
scopeRoots.clear();
return scope;
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph<Node> getControlFlowGraph() {
if (cfgs.peek() == null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
cfa.process(null, getScopeRoot());
cfgs.pop();
cfgs.push(cfa.getCfg());
}
return cfgs.peek();
}
/** Returns the current scope's root. */
public Node getScopeRoot() {
if (scopeRoots.isEmpty())
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> unless we can prove
* that it modifies external state. This is similar to
* {@code FlowSensitiveInlineVariables}, except that it works for variables
* used across scopes.
*
*/
class RemoveUnusedVars
implements CompilerPass, OptimizeCalls.CallGraphCompilerPass {
private final AbstractCompiler compiler;
private final boolean removeGlobals;
private boolean preserveFunctionExpressionNames;
/**
* Keep track of variables that we've referenced.
*/
private final Set<Var> referenced = Sets.newHashSet();
/**
* Keep track of variables that might be unreferenced.
*/
private final List<Var> maybeUnreferenced = Lists.newArrayList();
/**
* Keep track of scopes that we've traversed.
*/
private final List<Scope> allFunctionScopes = Lists.newArrayList();
/**
* Keep track of assigns to variables that we haven't referenced.
*/
private final Multimap<Var, Assign> assignsByVar =
ArrayListMultimap.create();
/**
* The assigns, indexed by the NAME node that they assign to.
*/
private final Map<Node, Assign> assignsByNode = Maps.newHashMap();
/**
* Keep track of continuations that are finished iff the variable they're
* indexed by is referenced.
*/
private final Multimap<Var, Continuation> continuations =
ArrayListMultimap.create();
private boolean modifyCallSites;
private CallSiteOptimizer callSiteOptimizer;
RemoveUnusedVars(
AbstractCompiler compiler,
boolean removeGlobals,
boolean preserveFunctionExpressionNames,
boolean modifyCallSites) {
this.compiler = compiler;
this.removeGlobals = removeGlobals;
this.preserveFunctionExpressionNames = preserveFunctionExpressionNames;
this.modifyCallSites = modifyCallSites;
}
/**
* Traverses the root, removing all unused variables. Multiple traversals
* may occur to ensure all unused variables are removed.
*/
public void process(Node externs, Node root) {
SimpleDefinitionFinder defFinder = null;
if (modifyCallSites) {
// For testing, allow the SimpleDefinitionFinder to be build now.
defFinder = new SimpleDefinitionFinder(compiler);
defFinder.process(externs, root);
}
process(externs, root, defFinder);
}
@Override
public void process(
Node externs, Node root, SimpleDefinitionFinder defFinder) {
if (modifyCallSites) {
Preconditions.checkNotNull(defFinder);
callSiteOptimizer = new CallSiteOptimizer(compiler, defFinder);
}
traverseAndRemoveUnusedReferences(root);
}
/**
* Traverses a node recursively. Call this once per pass.
*/
private void traverseAndRemoveUnusedReferences(Node root) {
Scope scope = new SyntacticScopeCreator(compiler).createScope(root, null);
traverseNode(root, null, scope);
if (removeGlobals) {
collectMaybeUnreferencedVars(scope);
}
interpretAssigns();
removeUnreferencedVars();
for (Scope fnScope : allFunctionScopes) {
removeUn
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>referencedFunctionArgs(fnScope);
}
}
/**
* Traverses everything in the current scope and marks variables that
* are referenced.
*
* During traversal, we identify subtrees that will only be
* referenced if their enclosing variables are referenced. Instead of
* traversing those subtrees, we create a continuation for them,
* and traverse them lazily.
*/
private void traverseNode(Node n, Node parent, Scope scope) {
int type = n.getType();
Var var = null;
switch (type) {
case Token.FUNCTION:
// If this function is a removable var, then create a continuation
// for it instead of traversing immediately.
if (NodeUtil.isFunctionDeclaration(n)) {
var = scope.getVar(n.getFirstChild().getString());
}
if (var != null && isRemovableVar(var)) {
continuations.put(var, new Continuation(n, scope));
} else {
traverseFunction(n, scope);
}
return;
case Token.ASSIGN:
Assign maybeAssign = Assign.maybeCreateAssign(n);
if (maybeAssign != null) {
// Put this in the assign map. It might count as a reference,
// but we won't know that until we have an index of all assigns.
var = scope.getVar(maybeAssign.nameNode.getString());
if (var != null) {
assignsByVar.put(var, maybeAssign);
assignsByNode.put(maybeAssign.nameNode, maybeAssign);
if (isRemovableVar(var) &&
!maybeAssign.mayHaveSecondarySideEffects) {
// If the var is unreferenced and performing this assign has
// no secondary side effects, then we can create a continuation
// for it instead of traversing immediately.
continuations.put(var, new Continuation(n, scope));
return;
}
}
}
break;
case Token.NAME:
var = scope.getVar(n.getString());
if (parent.getType() == Token.VAR) {
Node value = n.getFirstChild();
if (value != null && var != null && isRemovableVar(var) &&
!NodeUtil.mayHaveSideEffects(value)) {
// If the var is unreferenced and creating its value has no side
// effects, then we can create a continuation for it instead
// of traversing immediately.
continuations.put(var, new Continuation(n, scope));
return;
}
} else {
// All name references that aren't declarations or assigns
// are references to other vars.
if (var != null) {
// If that var hasn't already been marked referenced, then
// start tracking it. If this is an assign, do nothing
// for now.
if (isRemovableVar(var)) {
if (!assignsByNode.containsKey(n)) {
markReferencedVar(var);
}
} else {
markReferencedVar(var);
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> }
}
break;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
traverseNode(c, n, scope);
}
}
private boolean isRemovableVar(Var var) {
// Global variables are off-limits if the user might be using them.
if (!removeGlobals && var.isGlobal()) {
return false;
}
// Referenced variables are off-limits.
if (referenced.contains(var)) {
return false;
}
// Exported variables are off-limits.
if (compiler.getCodingConvention().isExported(var.getName())) {
return false;
}
return true;
}
/**
* Traverses a function, which creates a new scope in javascript.
*
* Note that CATCH blocks also create a new scope, but only for the
* catch variable. Declarations within the block actually belong to the
* enclosing scope. Because we don't remove catch variables, there's
* no need to treat CATCH blocks differently like we do functions.
*/
private void traverseFunction(Node n, Scope parentScope) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getType() == Token.FUNCTION);
final Node body = n.getLastChild();
Preconditions.checkState(body.getNext() == null &&
body.getType() == Token.BLOCK);
Scope fnScope =
new SyntacticScopeCreator(compiler).createScope(n, parentScope);
traverseNode(body, n, fnScope);
collectMaybeUnreferencedVars(fnScope);
allFunctionScopes.add(fnScope);
}
/**
* For each variable in this scope that we haven't found a reference
* for yet, add it to the list of variables to check later.
*/
private void collectMaybeUnreferencedVars(Scope scope) {
for (Iterator<Var> it = scope.getVars(); it.hasNext(); ) {
Var var = it.next();
if (isRemovableVar(var)) {
maybeUnreferenced.add(var);
}
}
}
/**
* Removes unreferenced arguments from a function declaration and when
* possible the function's callSites.
*
* @param fnScope The scope inside the function
*/
private void removeUnreferencedFunctionArgs(Scope fnScope) {
// TODO(johnlenz): Update type registry for function signature changes.
Node function = fnScope.getRootNode();
Preconditions.checkState(function.getType() == Token.FUNCTION);
Node argList = getFunctionArgList(function);
boolean modifyCallers = modifyCallSites
&& callSiteOptimizer.canModifyCallers(function);
if (!modifyCallers) {
// Strip unreferenced args off the end of the function declaration.
Node lastArg;
while ((lastArg = argList.getLastChild()) != null) {
Var var = fnScope.getVar(lastArg.getString());
if (!referenced.contains(var)) {
Preconditions.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>checkNotNull(var == null);
argList.removeChild(lastArg);
compiler.reportCodeChange();
} else {
break;
}
}
} else {
callSiteOptimizer.optimize(fnScope, referenced);
}
}
/**
* @return the LP node containing the function parameters.
*/
private static Node getFunctionArgList(Node function) {
return function.getFirstChild().getNext();
}
private static class CallSiteOptimizer {
private final AbstractCompiler compiler;
private final SimpleDefinitionFinder defFinder;
CallSiteOptimizer(
AbstractCompiler compiler,
SimpleDefinitionFinder defFinder) {
this.compiler = compiler;
this.defFinder = defFinder;
}
public void optimize(Scope fnScope, Set<Var> referenced) {
Node function = fnScope.getRootNode();
Preconditions.checkState(function.getType() == Token.FUNCTION);
Node argList = getFunctionArgList(function);
// In this path we try to modify all the call sites to remove unused
// function parameters.
boolean changeCallSignature = canChangeSignature(function);
removeUnreferencedFunctionArgs(
fnScope, function, referenced,
argList.getFirstChild(), 0, changeCallSignature);
}
/**
* For each unused function parameter, determine if it can be removed
* from all the call sites, if so, remove it from the function signature
* and the call sites otherwise replace the unused value where possible
* with a constant (0).
*
* @param scope The function scope
* @param function The function
* @param param The current parameter node in the parameter list.
* @param paramIndex The index of the current parameter
* @param canChangeSignature Whether function signature can be change.
* @return Whether there is a following function parameter.
*/
private boolean removeUnreferencedFunctionArgs(
Scope scope, Node function, Set<Var> referenced,
Node param, int paramIndex,
boolean canChangeSignature) {
if (param != null) {
// Take care of the following siblings first.
boolean hasFollowing = removeUnreferencedFunctionArgs(
scope, function, referenced, param.getNext(), paramIndex+1,
canChangeSignature);
Var var = scope.getVar(param.getString());
if (!referenced.contains(var)) {
Preconditions.checkNotNull(var);
// Remove call parameter if we can generally change the signature
// or if it is the last parameter in the parameter list.
boolean modifyAllCallSites = canChangeSignature || !hasFollowing;
if (modifyAllCallSites) {
modifyAllCallSites = canRemoveArgFromCallSites(
function, paramIndex);
}
tryRemoveArgFromCallSites(function, paramIndex, modifyAllCallSites);
// Remove an unused function parameter if all the call sites can
// be modified to remove it, or if it is the last parameter.
if (modifyAllCallSites || !hasFollowing) {
getFunctionArgList(function).removeChild(param);
compiler.reportCodeChange();
return hasFollowing;
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
}
return true;
} else {
// Anything past the last formal parameter can be removed from the call
// sites.
tryRemoveAllFollowingArgs(function, paramIndex-1);
return false;
}
}
/**
* Remove all references to a parameter, otherwise simplify the known
* references.
* @return Whether all the references were removed.
*/
private boolean canRemoveArgFromCallSites(Node function, int argIndex) {
Definition definition = getFunctionDefinition(function);
// Check all the call sites.
for (UseSite site : defFinder.getUseSites(definition)) {
if (isModifableCallSite(site)) {
Node arg = NodeUtil.getArgumentForCallOrNew(
site.node.getParent(), argIndex);
// TODO(johnlenz): try to remove parameters with side-effects by
// decomposing the call expression.
if (arg != null && NodeUtil.mayHaveSideEffects(arg, compiler)) {
return false;
}
} else {
return false;
}
}
return true;
}
/**
* Remove all references to a parameter if possible otherwise simplify the
* side-effect free parameters.
*/
private void tryRemoveArgFromCallSites(
Node function, int argIndex, boolean canModifyAllSites) {
Definition definition = getFunctionDefinition(function);
for (UseSite site : defFinder.getUseSites(definition)) {
if (isModifableCallSite(site)) {
Node arg = NodeUtil.getArgumentForCallOrNew(
site.node.getParent(), argIndex);
if (arg != null) {
Node argParent = arg.getParent();
// Even if we can't change the signature in general we can always
// remove an unused value off the end of the parameter list.
if (canModifyAllSites
|| (arg.getNext() == null
&& !NodeUtil.mayHaveSideEffects(arg, compiler))) {
// Remove the arg completely
argParent.removeChild(arg);
compiler.reportCodeChange();
} else {
// replace the node in the arg with 0
if (!NodeUtil.mayHaveSideEffects(arg, compiler)
&& (arg.getType() != Token.NUMBER || arg.getDouble() != 0)) {
argParent.replaceChild(
arg, Node.newNumber(0).copyInformationFrom(arg));
compiler.reportCodeChange();
}
}
}
}
}
}
/**
* Remove all the following parameters without side-effects
*/
private void tryRemoveAllFollowingArgs(Node function, final int argIndex) {
Definition definition = getFunctionDefinition(function);
for (UseSite site : defFinder.getUseSites(definition)) {
if (!isModifableCallSite(site)) {
continue;
}
Node arg = NodeUtil.getArgumentForCallOrNew(
site.node.getParent(), argIndex);
while (arg != null) {
Node next = arg.getNext();
if (next
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> != null && !NodeUtil.mayHaveSideEffects(next)) {
arg.getParent().removeChildAfter(arg);
compiler.reportCodeChange();
} else {
arg = next;
}
}
}
}
/**
* @param function
* @return Whether the callers to this function can be modified in any way.
*/
boolean canModifyCallers(Node function) {
if (NodeUtil.isVarArgsFunction(function)) {
return false;
}
DefinitionSite defSite = defFinder.getDefinitionForFunction(function);
if (defSite == null) {
return false;
}
Definition definition = defSite.definition;
// Be conservative, don't try to optimize any declaration that isn't as
// simple function declaration or assignment.
if (!SimpleDefinitionFinder.isSimpleFunctionDeclaration(function)) {
return false;
}
// Assume an exported method result is used, and the definition might be
// changed.
if (SimpleDefinitionFinder.maybeExported(compiler, definition)) {
return false;
}
Collection<UseSite> useSites = defFinder.getUseSites(definition);
for (UseSite site : useSites) {
// Multiple definitions prevent rewrite.
// TODO(johnlenz): Allow rewrite all definitions are valid.
Node nameNode = site.node;
Collection<Definition> singleSiteDefinitions =
defFinder.getDefinitionsReferencedAt(nameNode);
if (singleSiteDefinitions.size() > 1) {
return false;
}
Preconditions.checkState(!singleSiteDefinitions.isEmpty());
Preconditions.checkState(singleSiteDefinitions.contains(definition));
}
return true;
}
/**
* @param site The site to inspect
* @return Whether the call site is suitable for modification
*/
private static boolean isModifableCallSite(UseSite site) {
return SimpleDefinitionFinder.isCallOrNewSite(site)
&& !NodeUtil.isFunctionObjectCallOrApply(site.node.getParent());
}
/**
* @return Whether the definitionSite represents a function whose call
* signature can be modified.
*/
private boolean canChangeSignature(Node function) {
Definition definition = getFunctionDefinition(function);
Preconditions.checkState(!definition.isExtern());
Collection<UseSite> useSites = defFinder.getUseSites(definition);
for (UseSite site : useSites) {
// Accessing the property directly prevents rewrite.
if (!SimpleDefinitionFinder.isCallOrNewSite(site)) {
return false;
}
// TODO(johnlenz): support .call signature changes.
if (NodeUtil.isFunctionObjectCallOrApply(site.node.getParent())) {
return false;
}
// TODO(johnlenz): support specialization
// Multiple definitions prevent rewrite.
// Attempt to validate the state of the simple definition finder.
Node nameNode = site.node;
Collection<Definition> singleSiteDefinitions =
defFinder.getDefinitionsReferencedAt(nameNode);
Preconditions.checkState(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>singleSiteDefinitions.size() == 1);
Preconditions.checkState(singleSiteDefinitions.contains(definition));
}
return true;
}
/**
* @param function
* @return the Definition object for the function.
*/
private Definition getFunctionDefinition(Node function) {
DefinitionSite definitionSite = defFinder.getDefinitionForFunction(
function);
Preconditions.checkNotNull(definitionSite);
Definition definition = definitionSite.definition;
Preconditions.checkState(!definitionSite.inExterns);
Preconditions.checkState(definition.getRValue() == function);
return definition;
}
}
/**
* Look at all the property assigns to all variables.
* These may or may not count as references. For example,
*
* <code>
* var x = {};
* x.foo = 3; // not a reference.
* var y = foo();
* y.foo = 3; // is a reference.
* </code>
*
* Interpreting assigments could mark a variable as referenced that
* wasn't referenced before, in order to keep it alive. Because we find
* references by lazily traversing subtrees, marking a variable as
* referenced could trigger new traversals of new subtrees, which could
* find new references.
*
* Therefore, this interpretation needs to be run to a fixed point.
*/
private void interpretAssigns() {
boolean changes = false;
do {
changes = false;
// We can't use traditional iterators and iterables for this list,
// because our lazily-evaluated continuations will modify it while
// we traverse it.
for (int current = 0; current < maybeUnreferenced.size(); current++) {
Var var = maybeUnreferenced.get(current);
if (referenced.contains(var)) {
maybeUnreferenced.remove(current);
current--;
} else {
boolean assignedToUnknownValue = false;
boolean hasPropertyAssign = false;
if (var.getParentNode().getType() == Token.VAR &&
!NodeUtil.isForIn(var.getParentNode().getParent())) {
Node value = var.getInitialValue();
assignedToUnknownValue = value != null &&
!NodeUtil.isLiteralValue(value, true);
} else {
// This was initialized to a function arg or a catch param
// or a for...in variable.
assignedToUnknownValue = true;
}
for (Assign assign : assignsByVar.get(var)) {
if (assign.isPropertyAssign) {
hasPropertyAssign = true;
} else if (!NodeUtil.isLiteralValue(
assign.assignNode.getLastChild(), true)) {
assignedToUnknownValue = true;
}
}
if (assignedToUnknownValue && hasPropertyAssign) {
changes = markReferencedVar(var) || changes;
maybeUnreferenced.remove(current);
current--;
}
}
}
} while (changes);
}
/**
* Remove all assigns to a
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> var.
*/
private void removeAllAssigns(Var var) {
for (Assign assign : assignsByVar.get(var)) {
assign.remove();
compiler.reportCodeChange();
}
}
/**
* Marks a var as referenced, recursing into any values of this var
* that we skipped.
* @return True if this variable had not been referenced before.
*/
private boolean markReferencedVar(Var var) {
if (referenced.add(var)) {
for (Continuation c : continuations.get(var)) {
c.apply();
}
return true;
}
return false;
}
/**
* Removes any vars in the scope that were not referenced. Removes any
* assigments to those variables as well.
*/
private void removeUnreferencedVars() {
CodingConvention convention = compiler.getCodingConvention();
for (Iterator<Var> it = maybeUnreferenced.iterator(); it.hasNext(); ) {
Var var = it.next();
// Regardless of what happens to the original declaration,
// we need to remove all assigns, because they may contain references
// to other unreferenced variables.
removeAllAssigns(var);
compiler.addToDebugLog("Unreferenced var: " + var.name);
Node nameNode = var.nameNode;
Node toRemove = nameNode.getParent();
Node parent = toRemove.getParent();
Preconditions.checkState(
toRemove.getType() == Token.VAR ||
toRemove.getType() == Token.FUNCTION ||
toRemove.getType() == Token.LP &&
parent.getType() == Token.FUNCTION,
"We should only declare vars and functions and function args");
if (toRemove.getType() == Token.LP &&
parent.getType() == Token.FUNCTION) {
// Don't remove function arguments here. That's a special case
// that's taken care of in removeUnreferencedFunctionArgs.
} else if (NodeUtil.isFunctionExpression(toRemove)) {
if (!preserveFunctionExpressionNames) {
toRemove.getFirstChild().setString("");
compiler.reportCodeChange();
}
// Don't remove bleeding functions.
} else if (parent != null &&
parent.getType() == Token.FOR &&
parent.getChildCount() < 4) {
// foreach iterations have 3 children. Leave them alone.
} else if (toRemove.getType() == Token.VAR &&
nameNode.hasChildren() &&
NodeUtil.mayHaveSideEffects(nameNode.getFirstChild())) {
// If this is a single var declaration, we can at least remove the
// declaration itself and just leave the value, e.g.,
// var a = foo(); => foo();
if (toRemove.getChildCount() == 1) {
parent.replaceChild(toRemove,
new Node(Token.EXPR_RESULT, nameNode.removeFirstChild()));
compiler.reportCodeChange();
}
} else if (toRemove.getType() == Token.VAR &&
toRemove.getChildCount
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>() > 1) {
// For var declarations with multiple names (i.e. var a, b, c),
// only remove the unreferenced name
toRemove.removeChild(nameNode);
compiler.reportCodeChange();
} else if (parent != null) {
NodeUtil.removeChild(parent, toRemove);
compiler.reportCodeChange();
}
}
}
/**
* Our progress in a traversal can be expressed completely as the
* current node and scope. The continuation lets us save that
* information so that we can continue the traversal later.
*/
private class Continuation {
private final Node node;
private final Scope scope;
Continuation(Node node, Scope scope) {
this.node = node;
this.scope = scope;
}
void apply() {
if (NodeUtil.isFunctionDeclaration(node)) {
traverseFunction(node, scope);
} else {
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
traverseNode(child, node, scope);
}
}
}
}
private static class Assign {
final Node assignNode;
final Node nameNode;
// If false, then this is an assign to the normal variable. Otherwise,
// this is an assign to a property of that variable.
final boolean isPropertyAssign;
// Secondary side effects are any side effects in this assign statement
// that aren't caused by the assignment operation itself. For example,
// a().b = 3;
// a = b();
// var foo = (a = b);
// In the first two cases, the sides of the assignment have side-effects.
// In the last one, the result of the assignment is used, so we
// are conservative and assume that it may be used in a side-effecting
// way.
final boolean mayHaveSecondarySideEffects;
Assign(Node assignNode, Node nameNode, boolean isPropertyAssign) {
Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode));
this.assignNode = assignNode;
this.nameNode = nameNode;
this.isPropertyAssign = isPropertyAssign;
this.mayHaveSecondarySideEffects =
assignNode.getParent().getType() != Token.EXPR_RESULT ||
NodeUtil.mayHaveSideEffects(assignNode.getFirstChild()) ||
NodeUtil.mayHaveSideEffects(assignNode.getLastChild());
}
/**
* If this is an assign to a variable or its property, return it.
* Otherwise, return null.
*/
static Assign maybeCreateAssign(Node assignNode) {
Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode));
// Skip one level of GETPROPs or GETELEMs.
//
// Don't skip more than one level, because then we get into
// situations where assigns to properties of properties will always
// trigger side-effects, and the variable they're on cannot be removed.
boolean isPropAssign = false;
Node current = assignNode.getFirstChild();
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
if (NodeUtil.isGet(current)) {
current = current.getFirstChild();
isPropAssign = true;
if (current.getType() == Token.GETPROP &&
current.getLastChild().getString().equals("prototype")) {
// Prototype properties sets should be considered like normal
// property sets.
current = current.getFirstChild();
}
}
if (current.getType() == Token.NAME) {
return new Assign(assignNode, current, isPropAssign);
}
return null;
}
/**
* Replace the current assign with its right hand side.
*/
void remove() {
Node parent = assignNode.getParent();
if (mayHaveSecondarySideEffects) {
Node replacement = assignNode.getLastChild().detachFromParent();
// Aggregate any expressions in GETELEMs.
for (Node current = assignNode.getFirstChild();
current.getType() != Token.NAME;
current = current.getFirstChild()) {
if (current.getType() == Token.GETELEM) {
replacement = new Node(Token.COMMA,
current.getLastChild().detachFromParent(), replacement);
replacement.copyInformationFrom(current);
}
}
parent.replaceChild(assignNode, replacement);
} else {
Node gramps = parent.getParent();
if (parent.getType() == Token.EXPR_RESULT) {
gramps.removeChild(parent);
} else {
parent.replaceChild(assignNode,
assignNode.getLastChild().detachFromParent());
}
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> removeTryCatchFinally = false;
closurePass = false;
rewriteNewDateGoogNow = true;
removeAbstractMethods = true;
removeClosureAsserts = false;
stripTypes = Collections.emptySet();
stripNameSuffixes = Collections.emptySet();
stripNamePrefixes = Collections.emptySet();
stripTypePrefixes = Collections.emptySet();
customPasses = null;
markNoSideEffectCalls = false;
defineReplacements = Maps.newHashMap();
moveFunctionDeclarations = false;
instrumentationTemplate = null;
appNameStr = "";
recordFunctionInformation = false;
generateExports = false;
cssRenamingMap = null;
processObjectPropertyString = false;
idGenerators = Collections.emptySet();
replaceStringsFunctionDescriptions = Collections.emptyList();
replaceStringsPlaceholderToken = "";
// Output
printInputDelimiter = false;
prettyPrint = false;
lineBreak = false;
reportPath = null;
tracer = TracerMode.OFF;
colorizeErrorOutput = false;
errorFormat = ErrorFormat.SINGLELINE;
warningsGuard = null;
debugFunctionSideEffectsPath = null;
jsOutputFile = "";
externExports = false;
nameReferenceReportPath = null;
nameReferenceGraphPath = null;
}
/**
* Returns the map of define replacements.
*/
public Map<String, Node> getDefineReplacements() {
Map<String, Node> map = Maps.newHashMap();
for (Map.Entry<String, Object> entry : defineReplacements.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (value instanceof Boolean) {
map.put(name, ((Boolean) value).booleanValue() ?
new Node(Token.TRUE) : new Node(Token.FALSE));
} else if (value instanceof Integer) {
map.put(name, Node.newNumber(((Integer) value).intValue()));
} else if (value instanceof Double) {
map.put(name, Node.newNumber(((Double) value).doubleValue()));
} else {
Preconditions.checkState(value instanceof String);
map.put(name, Node.newString((String) value));
}
}
return map;
}
/**
* Sets the value of the {@code @define} variable in JS
* to a boolean literal.
*/
public void setDefineToBooleanLiteral(String defineName, boolean value) {
defineReplacements.put(defineName, new Boolean(value));
}
/**
* Sets the value of the {@code @define} variable in JS to a
* String literal.
*/
public void setDefineToStringLiteral(String defineName, String value) {
defineReplacements.put(defineName, value);
}
/**
* Sets the value of the {@code @define} variable in JS to a
* number literal.
*/
public void setDefineToNumberLiteral(String defineName, int value) {
defineReplacements.put(defineName, new Integer(value));
}
/**
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> setRemoveAbstractMethods(boolean remove) {
this.removeAbstractMethods = remove;
}
public void setRemoveClosureAsserts(boolean remove) {
this.removeClosureAsserts = remove;
}
/**
* If true, name anonymous functions only. All other passes will be skipped.
*/
public void setNameAnonymousFunctionsOnly(boolean value) {
this.nameAnonymousFunctionsOnly = value;
}
public void setColorizeErrorOutput(boolean colorizeErrorOutput) {
this.colorizeErrorOutput = colorizeErrorOutput;
}
public boolean shouldColorizeErrorOutput() {
return colorizeErrorOutput;
}
/**
* If true, chain calls to functions that return this.
*/
public void setChainCalls(boolean value) {
this.chainCalls = value;
}
/**
* Enable runtime type checking, which adds JS type assertions for debugging.
*
* @param logFunction A JS function to be used for logging runtime type
* assertion failures.
*/
public void enableRuntimeTypeCheck(String logFunction) {
this.runtimeTypeCheck = true;
this.runtimeTypeCheckLogFunction = logFunction;
}
public void disableRuntimeTypeCheck() {
this.runtimeTypeCheck = false;
}
public void setCodingConvention(CodingConvention codingConvention) {
this.codingConvention = codingConvention;
}
public CodingConvention getCodingConvention() {
return codingConvention;
}
/**
* Sort inputs by their goog.provide/goog.require calls, and prune inputs
* whose symbols are not required.
*/
public void setManageClosureDependencies(boolean newVal) {
manageClosureDependencies = newVal;
}
/**
* Sort inputs by their goog.provide/goog.require calls.
*
* @param entryPoints Entry points to the program. Must be goog.provide'd
* symbols. Any goog.provide'd symbols that are not a transitive
* dependency of the entry points will be deleted.
* Files without goog.provides, and their dependencies,
* will always be left in.
*/
public void setManageClosureDependencies(List<String> entryPoints) {
Preconditions.checkNotNull(entryPoints);
manageClosureDependencies = true;
manageClosureDependenciesEntryPoints = entryPoints;
}
/**
* Controls how detailed the compilation summary is. Values:
* 0 (never print summary), 1 (print summary only if there are
* errors or warnings), 2 (print summary if type checking is on,
* see --check_types), 3 (always print summary). The default level
* is 1
*/
public void setSummaryDetailLevel(int summaryDetailLevel) {
this.summaryDetailLevel = summaryDetailLevel;
}
public void enableExternExports(boolean enable) {
this.externExports = enable;
}
public boolean isExternExportsEnabled() {
return externExports;
}
/**
* Sets the output charset by name.
*/
public void setOutputCharset(String charsetName) {
this.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* An AST generated totally by the compiler.
*
* @author nicksantos@google.com (Nick Santos)
*/
class SyntheticAst implements SourceAst {
private static final long serialVersionUID = 1L;
private final String sourceName;
private Node root;
SyntheticAst(String sourceName) {
this.sourceName = sourceName;
clearAst();
}
@Override
public Node getAstRoot(AbstractCompiler compiler) {
return root;
}
@Override
public void clearAst() {
root = new Node(Token.SCRIPT);
root.putProp(Node.SOURCENAME_PROP, sourceName);
}
@Override
public SourceFile getSourceFile() {
return null;
}
@Override
public void setSourceFile(SourceFile file) {
throw new IllegalStateException(
"Cannot set a source file for a synthetic AST");
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>process(externs, root);
compiler.setNormalized();
}
/**
* Propagate constant annotations over the Var graph.
*/
static class PropagateConstantAnnotationsOverVars
extends AbstractPostOrderCallback
implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean assertOnChange;
PropagateConstantAnnotationsOverVars(
AbstractCompiler compiler, boolean forbidChanges) {
this.compiler = compiler;
this.assertOnChange = forbidChanges;
}
@Override
public void process(Node externs, Node root) {
new NodeTraversal(compiler, this).traverseRoots(externs, root);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Note: Constant properties annotations are not propagated.
if (n.getType() == Token.NAME) {
if (n.getString().isEmpty()) {
return;
}
JSDocInfo info = null;
// Find the JSDocInfo for a top level variable.
Var var = t.getScope().getVar(n.getString());
if (var != null) {
info = var.getJSDocInfo();
}
boolean shouldBeConstant =
(info != null && info.isConstant()) ||
NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), n, parent);
boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (shouldBeConstant && !isMarkedConstant) {
if (assertOnChange) {
String name = n.getString();
throw new IllegalStateException(
"Unexpected const change.\n" +
" name: "+ name + "\n" +
" parent:" + n.getParent().toStringTree());
}
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
}
}
/**
* Walk the AST tree and verify that constant names are used consistently.
*/
static class VerifyConstants extends AbstractPostOrderCallback
implements CompilerPass {
final private AbstractCompiler compiler;
final private boolean checkUserDeclarations;
VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) {
this.compiler = compiler;
this.checkUserDeclarations = checkUserDeclarations;
}
@Override
public void process(Node externs, Node root) {
Node externsAndJs = root.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(externsAndJs.hasChild(externs));
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
private Map<String, Boolean> constantMap = Maps.newHashMap();
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
String name = n.getString();
if (n.getString().isEmpty()) {
return;
}
boolean isConst = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (checkUserDeclarations) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> boolean expectedConst = false;
CodingConvention convention = compiler.getCodingConvention();
if (NodeUtil.isConstantName(n)
|| NodeUtil.isConstantByConvention(convention, n, parent)) {
expectedConst = true;
} else {
expectedConst = false;
JSDocInfo info = null;
Var var = t.getScope().getVar(n.getString());
if (var != null) {
info = var.getJSDocInfo();
}
if (info != null && info.isConstant()) {
expectedConst = true;
} else {
expectedConst = false;
}
}
if (expectedConst) {
Preconditions.checkState(expectedConst == isConst,
"The name " + name + " is not annotated as constant.");
} else {
Preconditions.checkState(expectedConst == isConst,
"The name " + name + " should not be annotated as constant.");
}
}
Boolean value = constantMap.get(name);
if (value == null) {
constantMap.put(name, isConst);
} else {
Preconditions.checkState(value.booleanValue() == isConst,
"The name " + name + " is not consistently annotated as " +
"constant.");
}
}
}
}
/**
* Simplify the AST:
* - VAR declarations split, so they represent exactly one child
* declaration.
* - WHILEs are converted to FORs
* - FOR loop are initializers are moved out of the FOR structure
* - LABEL node of children other than LABEL, BLOCK, WHILE, FOR, or DO are
* moved into a block.
* - Add constant annotations based on coding convention.
*/
static class NormalizeStatements implements Callback {
private final AbstractCompiler compiler;
private final boolean assertOnChange;
NormalizeStatements(AbstractCompiler compiler, boolean assertOnChange) {
this.compiler = compiler;
this.assertOnChange = assertOnChange;
}
private void reportCodeChange(String changeDescription) {
if (assertOnChange) {
throw new IllegalStateException(
"Normalize constraints violated:\n" + changeDescription);
}
compiler.reportCodeChange();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
doStatementNormalizations(t, n, parent);
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.WHILE:
if (CONVERT_WHILE_TO_FOR) {
Node expr = n.getFirstChild();
n.setType(Token.FOR);
Node empty = new Node(Token.EMPTY);
empty.copyInformationFrom(n);
n.addChildBefore(empty, expr);
n.addChildAfter(empty.cloneNode(), expr);
reportCodeChange("WHILE node");
}
break;
case Token.FUNCTION:
normalizeFunctionDeclaration(n);
break;
case Token
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.NAME:
case Token.STRING:
case Token.GET:
case Token.SET:
annotateConstantsByConvention(n, parent);
break;
}
}
/**
* Mark names and properties that are constants by convention.
*/
private void annotateConstantsByConvention(Node n, Node parent) {
Preconditions.checkState(
n.getType() == Token.NAME
|| n.getType() == Token.STRING
|| n.getType() == Token.GET
|| n.getType() == Token.SET);
// There are only two cases where a string token
// may be a variable reference: The right side of a GETPROP
// or an OBJECTLIT key.
boolean isObjLitKey = NodeUtil.isObjectLitKey(n, parent);
boolean isProperty = isObjLitKey ||
(parent.getType() == Token.GETPROP &&
parent.getLastChild() == n);
if (n.getType() == Token.NAME || isProperty) {
boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (!isMarkedConstant &&
NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), n, parent)) {
if (assertOnChange) {
String name = n.getString();
throw new IllegalStateException(
"Unexpected const change.\n" +
" name: "+ name + "\n" +
" parent:" + n.getParent().toStringTree());
}
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
}
/**
* Rewrite named unhoisted functions declarations to a known
* consistent behavior so we don't to different logic paths for the same
* code. From:
* function f() {}
* to:
* var f = function () {};
*/
private void normalizeFunctionDeclaration(Node n) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
if (!NodeUtil.isFunctionExpression(n)
&& !NodeUtil.isHoistedFunctionDeclaration(n)) {
rewriteFunctionDeclaration(n);
}
}
/**
* Rewrite the function declaration from:
* function x() {}
* FUNCTION
* NAME
* LP
* BLOCK
* to:
* var x = function() {};
* VAR
* NAME
* FUNCTION
* NAME (w/ empty string)
* LP
* BLOCK
*/
private void rewriteFunctionDeclaration(Node n) {
// Prepare a spot for the function.
Node oldNameNode = n.getFirstChild();
Node fnNameNode = oldNameNode.cloneNode();
Node var = new Node(Token.VAR, fnNameNode, n.getLineno(), n.getCharno());
var.copyInformationFrom(n);
// Prepare the function
oldNameNode.setString("");
// Move the function
Node parent = n.getParent();
parent.replaceChild(n, var);
fnNameNode.addChildToFront(n);
reportCodeChange
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>("Function declaration");
}
/**
* Do normalizations that introduce new siblings or parents.
*/
private void doStatementNormalizations(
NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.LABEL) {
normalizeLabels(n);
}
// Only inspect the children of SCRIPTs, BLOCKs and LABELs, as all these
// are the only legal place for VARs and FOR statements.
if (NodeUtil.isStatementBlock(n) || n.getType() == Token.LABEL) {
extractForInitializer(n, null, null);
}
// Only inspect the children of SCRIPTs, BLOCKs, as all these
// are the only legal place for VARs.
if (NodeUtil.isStatementBlock(n)) {
splitVarDeclarations(n);
}
if (n.getType() == Token.FUNCTION) {
moveNamedFunctions(n.getLastChild());
}
}
// TODO(johnlenz): Move this to NodeTypeNormalizer once the unit tests are
// fixed.
/**
* Limit the number of special cases where LABELs need to be handled. Only
* BLOCK and loops are allowed to be labeled. Loop labels must remain in
* place as the named continues are not allowed for labeled blocks.
*/
private void normalizeLabels(Node n) {
Preconditions.checkArgument(n.getType() == Token.LABEL);
Node last = n.getLastChild();
switch (last.getType()) {
case Token.LABEL:
case Token.BLOCK:
case Token.FOR:
case Token.WHILE:
case Token.DO:
return;
default:
Node block = new Node(Token.BLOCK);
block.copyInformationFrom(last);
n.replaceChild(last, block);
block.addChildToFront(last);
reportCodeChange("LABEL normalization");
return;
}
}
/**
* Bring the initializers out of FOR loops. These need to be placed
* before any associated LABEL nodes. This needs to be done from the top
* level label first so this is called as a pre-order callback (from
* shouldTraverse).
*
* @param n The node to inspect.
* @param before The node to insert the initializer before.
* @param beforeParent The parent of the node before which the initializer
* will be inserted.
*/
private void extractForInitializer(
Node n, Node before, Node beforeParent) {
for (Node next, c = n.getFirstChild(); c != null; c = next) {
next = c.getNext();
Node insertBefore = (before == null) ? c : before;
Node insertBeforeParent = (before == null) ? n : beforeParent;
switch (c.getType()) {
case Token.LABEL:
extractForInitializer(c, insertBefore, insertBeforeParent);
break;
case Token.FOR:
if (NodeUtil.isForIn(c)) {
Node first = c.getFirstChild();
if (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>first.getType() == Token.VAR) {
// Transform:
// for (var a in b) {}
// to:
// var a; for (a in b) {};
Node newStatement = first.cloneTree();
Node name = first.removeFirstChild();
first.getParent().replaceChild(first, name);
insertBeforeParent.addChildBefore(newStatement, insertBefore);
reportCodeChange("FOR-IN var declaration");
}
} else if (c.getFirstChild().getType() != Token.EMPTY) {
Node init = c.getFirstChild();
Node empty = new Node(Token.EMPTY);
empty.copyInformationFrom(c);
c.replaceChild(init, empty);
Node newStatement;
// Only VAR statements, and expressions are allowed,
// but are handled differently.
if (init.getType() == Token.VAR) {
newStatement = init;
} else {
newStatement = NodeUtil.newExpr(init);
}
insertBeforeParent.addChildBefore(newStatement, insertBefore);
reportCodeChange("FOR initializer");
}
break;
}
}
}
/**
* Split a var node such as:
* var a, b;
* into individual statements:
* var a;
* var b;
* @param n The whose children we should inspect.
*/
private void splitVarDeclarations(Node n) {
for (Node next, c = n.getFirstChild(); c != null; c = next) {
next = c.getNext();
if (c.getType() == Token.VAR) {
if (assertOnChange && !c.hasChildren()) {
throw new IllegalStateException("Empty VAR node.");
}
while (c.getFirstChild() != c.getLastChild()) {
Node name = c.getFirstChild();
c.removeChild(name);
Node newVar = new Node(
Token.VAR, name, n.getLineno(), n.getCharno());
n.addChildBefore(newVar, c);
reportCodeChange("VAR with multiple children");
}
}
}
}
/**
* Move all the functions that are valid at the execution of the first
* statement of the function to the beginning of the function definition.
*/
private void moveNamedFunctions(Node functionBody) {
Preconditions.checkState(
functionBody.getParent().getType() == Token.FUNCTION);
Node previous = null;
Node current = functionBody.getFirstChild();
// Skip any declarations at the beginning of the function body, they
// are already in the right place.
while (current != null && NodeUtil.isFunctionDeclaration(current)) {
previous = current;
current = current.getNext();
}
// Find any remaining declarations and move them.
Node insertAfter = previous;
while (current != null) {
// Save off the next node as the current node maybe removed.
Node next = current.getNext();
if (NodeUtil.isFunctionDeclaration(current)) {
// Remove the declaration from the body.
Preconditions
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.checkNotNull(previous);
functionBody.removeChildAfter(previous);
// Readd the function at the top of the function body (after any
// previous declarations).
insertAfter = addToFront(functionBody, current, insertAfter);
reportCodeChange("Move function declaration not at top of function");
} else {
// Update the previous only if the current node hasn't been moved.
previous = current;
}
current = next;
}
}
/**
* @param after The child node to insert the newChild after, or null if
* newChild should be added to the front of parent's child list.
* @return The inserted child node.
*/
private Node addToFront(Node parent, Node newChild, Node after) {
if (after == null) {
parent.addChildToFront(newChild);
} else {
parent.addChildAfter(newChild, after);
}
return newChild;
}
}
/**
* Remove duplicate VAR declarations.
*/
private void removeDuplicateDeclarations(Node externs, Node root) {
Callback tickler = new ScopeTicklingCallback();
ScopeCreator scopeCreator = new SyntacticScopeCreator(
compiler, new DuplicateDeclarationHandler());
NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator);
t.traverseRoots(externs, root);
}
/**
* ScopeCreator duplicate declaration handler.
*/
private final class DuplicateDeclarationHandler implements
SyntacticScopeCreator.RedeclarationHandler {
private Set<Var> hasOkDuplicateDeclaration = Sets.newHashSet();
/**
* Remove duplicate VAR declarations encountered discovered during
* scope creation.
*/
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Preconditions.checkState(n.getType() == Token.NAME);
Node parent = n.getParent();
Var v = s.getVar(name);
if (v != null && s.isGlobal()) {
// We allow variables to be duplicate declared if one
// declaration appears in source and the other in externs.
// This deals with issues where a browser built-in is declared
// in one browser but not in another.
if (v.isExtern() && !input.isExtern()) {
if (hasOkDuplicateDeclaration.add(v)) {
return;
}
}
}
// If name is "arguments", Var maybe null.
if (v != null && v.getParentNode().getType() == Token.CATCH) {
// Redeclaration of a catch expression variable is hard to model
// without support for "with" expressions.
// The EcmaScript spec (section 12.14), declares that a catch
// "catch (e) {}" is handled like "with ({'e': e}) {}" so that
// "var e" would refer to the scope variable, but any following
// reference would still refer to "e" of the catch expression.
// Until we have support for this disallow it.
// Currently the Scope object
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> adds the catch expression to the
// function scope, which is technically not true but a good
// approximation for most uses.
// TODO(johnlenz): Consider improving how scope handles catch
// expression.
// Use the name of the var before it was made unique.
name = MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName(
name);
compiler.report(
JSError.make(
input.getName(), n,
CATCH_BLOCK_VAR_ERROR, name));
} else if (v != null && parent.getType() == Token.FUNCTION) {
if (v.getParentNode().getType() == Token.VAR) {
s.undeclare(v);
s.declare(name, n, n.getJSType(), v.input);
replaceVarWithAssignment(v.getNameNode(), v.getParentNode(),
v.getParentNode().getParent());
}
} else if (parent.getType() == Token.VAR) {
Preconditions.checkState(parent.hasOneChild());
replaceVarWithAssignment(n, parent, parent.getParent());
}
}
/**
* Remove the parent VAR. There are three cases that need to be handled:
* 1) "var a = b;" which is replaced with "a = b"
* 2) "label:var a;" which is replaced with "label:;". Ideally, the
* label itself would be removed but that is not possible in the
* context in which "onRedeclaration" is called.
* 3) "for (var a in b) ..." which is replaced with "for (a in b)..."
* Cases we don't need to handle are VARs with multiple children,
* which have already been split into separate declarations, so there
* is no need to handle that here, and "for (var a;;);", which has
* been moved out of the loop.
* The result of this is that in each case the parent node is replaced
* which is generally dangerous in a traversal but is fine here with
* the scope creator, as the next node of interest is the parent's
* next sibling.
*/
private void replaceVarWithAssignment(Node n, Node parent, Node gramps) {
if (n.hasChildren()) {
// The * is being initialize, preserve the new value.
parent.removeChild(n);
// Convert "var name = value" to "name = value"
Node value = n.getFirstChild();
n.removeChild(value);
Node replacement = new Node(Token.ASSIGN, n, value);
replacement.copyInformationFrom(parent);
gramps.replaceChild(parent, NodeUtil.newExpr(replacement));
} else {
// It is an empty reference remove it.
if (NodeUtil.isStatementBlock(gramps)) {
gramps.removeChild(parent);
} else if (gramps.getType() == Token.FOR) {
// This is the "for (var a in
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> b)..." case. We don't need to worry
// about initializers in "for (var a;;)..." as those are moved out
// as part of the other normalizations.
parent.removeChild(n);
gramps.replaceChild(parent, n);
} else {
Preconditions.checkState(gramps.getType() == Token.LABEL);
// We should never get here. LABELs with a single VAR statement should
// already have been normalized to have a BLOCK.
throw new IllegalStateException("Unexpected LABEL");
}
}
reportCodeChange("Duplicate VAR declaration");
}
}
/**
* A simple class that causes scope to be created.
*/
private final class ScopeTicklingCallback
implements NodeTraversal.ScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
// Cause the scope to be created, which will cause duplicate
// to be found.
t.getScope();
}
@Override
public void exitScope(NodeTraversal t) {
// Nothing to do.
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Nothing to do.
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> return expr;
} else {
return new JSTypeExpression(
new Node(Token.EQUALS, expr.root), expr.sourceName);
}
}
/**
* @return Whether this expression denotes an optional {@code @param}.
*/
public boolean isOptionalArg() {
return root.getType() == Token.EQUALS;
}
/**
* @return Whether this expression denotes a rest args {@code @param}.
*/
public boolean isVarArgs() {
return root.getType() == Token.ELLIPSIS;
}
/**
* Evaluates the type expression into a {@code JSType} object.
*/
public JSType evaluate(StaticScope<JSType> scope, JSTypeRegistry registry) {
return registry.createFromTypeNodes(root, sourceName, scope,
root.getBooleanProp(Node.BRACELESS_TYPE));
}
@Override
public boolean equals(Object other) {
return other instanceof JSTypeExpression &&
((JSTypeExpression) other).root.checkTreeEqualsSilent(root);
}
@Override
public int hashCode() {
return root.toStringTree().hashCode();
}
Node getRoot() {
return root;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.length);
assertEquals(expectedSymbolTableError, stErrors[0].getType());
} else {
assertEquals("Unexpected symbol table error(s): " +
Joiner.on("\n").join(stErrors),
0, stErrors.length);
}
if (warning == null) {
assertEquals(
"Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings),
0, aggregateWarningCount);
} else {
assertEquals("There should be one warning, repeated " + numRepetitions +
" time(s).", numRepetitions, aggregateWarningCount);
for (int i = 0; i < numRepetitions; ++i) {
JSError[] warnings = errorManagers[i].getWarnings();
JSError actual = warnings[0];
assertEquals(warning, actual.getType());
// Make sure that source information is always provided.
if (!allowSourcelessWarnings) {
assertTrue("Missing source file name in warning",
actual.sourceName != null && !actual.sourceName.isEmpty());
assertTrue("Missing line number in warning",
-1 != actual.lineNumber);
assertTrue("Missing char number in warning",
-1 != actual.getCharno());
}
if (description != null) {
assertEquals(description, actual.description);
}
}
}
if (normalizeEnabled) {
normalizeActualCode(compiler, externsRootClone, mainRootClone);
}
if (mainRootClone.checkTreeEqualsSilent(mainRoot)) {
assertFalse(
"compiler.reportCodeChange() was called " +
"even though nothing changed",
hasCodeChanged);
} else {
assertTrue("compiler.reportCodeChange() should have been called",
hasCodeChanged);
}
if (compareAsTree) {
String explanation = expectedRoot.checkTreeEquals(mainRoot);
assertNull("\nExpected: " + compiler.toSource(expectedRoot) +
"\nResult: " + compiler.toSource(mainRoot) +
"\n" + explanation, explanation);
} else if (expected != null) {
assertEquals(
Joiner.on("").join(expected), compiler.toSource(mainRoot));
}
// Verify normalization is not invalidated.
Node normalizeCheckRootClone = root.cloneTree();
Node normalizeCheckExternsRootClone = root.getFirstChild();
Node normalizeCheckMainRootClone = root.getLastChild();
new PrepareAst(compiler).process(
normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
String explanation =
normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
assertNull("Node structure normalization invalidated.\nExpected: " +
compiler.toSource(normalizeCheckMainRootClone) +
"\nResult: " + compiler.toSource(mainRoot) +
"\n" + explanation, explanation);
// TODO(johnlenz): enable this for most test cases.
// Currently, this invalidates test for while-loops, for-loop
// initializers, and other naming.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> However, a set of code
// (FoldConstants, etc) runs before the Normalize pass, so this can't be
// force on everywhere.
if (normalizeEnabled) {
new Normalize(compiler, true).process(
normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
assertNull("Normalization invalidated.\nExpected: " +
compiler.toSource(normalizeCheckMainRootClone) +
"\nResult: " + compiler.toSource(mainRoot) +
"\n" + explanation, explanation);
}
} else {
String errors = "";
for (JSError actualError : compiler.getErrors()) {
errors += actualError.description + "\n";
}
assertEquals("There should be one error. " + errors,
1, compiler.getErrorCount());
assertEquals(errors, error, compiler.getErrors()[0].getType());
if (warning != null) {
String warnings = "";
for (JSError actualError : compiler.getWarnings()) {
warnings += actualError.description + "\n";
}
assertEquals("There should be one warning. " + warnings,
1, compiler.getWarningCount());
assertEquals(warnings, warning, compiler.getWarnings()[0].getType());
}
}
}
private void normalizeActualCode(
Compiler compiler, Node externsRoot, Node mainRoot) {
Normalize normalize = new Normalize(compiler, false);
normalize.process(externsRoot, mainRoot);
}
/**
* Parses expected js inputs and returns the root of the parse tree.
*/
protected Node parseExpectedJs(String[] expected) {
Compiler compiler = createCompiler();
JSSourceFile[] inputs = new JSSourceFile[expected.length];
for (int i = 0; i < expected.length; i++) {
inputs[i] = JSSourceFile.fromCode("expected" + i, expected[i]);
}
compiler.init(externsInputs, inputs, getOptions());
Node root = compiler.parseInputs();
assertTrue("Unexpected parse error(s): " +
Joiner.on("\n").join(compiler.getErrors()), root != null);
Node externsRoot = root.getFirstChild();
Node mainRoot = externsRoot.getNext();
// Only run the normalize pass, if asked.
if (normalizeEnabled && normalizeExpected && !compiler.hasErrors()) {
Normalize normalize = new Normalize(compiler, false);
normalize.process(externsRoot, mainRoot);
}
return mainRoot;
}
protected Node parseExpectedJs(String expected) {
return parseExpectedJs(new String[] {expected});
}
/**
* Generates a list of modules from a list of inputs, such that each module
* depends on the module before it.
*/
static JSModule[] createModuleChain(String... inputs) {
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>i].addDependency(modules[i - 1]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs, such that each module
* depends on the first module.
*/
static JSModule[] createModuleStar(String... inputs) {
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[0]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs, such that modules
* form a bush formation. In a bush formation, module 2 depends
* on module 1, and all other modules depend on module 2.
*/
static JSModule[] createModuleBush(String ... inputs) {
Preconditions.checkState(inputs.length > 2);
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[i == 1 ? 0 : 1]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs, such that modules
* form a tree formation. In a tree formation, module N depends on
* module `floor(N/2)`, So the modules form a balanced binary tree.
*/
static JSModule[] createModuleTree(String ... inputs) {
JSModule[] modules = createModules(inputs);
for (int i = 1; i < modules.length; i++) {
modules[i].addDependency(modules[(i - 1) / 2]);
}
return modules;
}
/**
* Generates a list of modules from a list of inputs. Does not generate any
* dependencies between the modules.
*/
static JSModule[] createModules(String... inputs) {
JSModule[] modules = new JSModule[inputs.length];
for (int i = 0; i < inputs.length; i++) {
JSModule module = modules[i] = new JSModule("m" + i);
module.add(JSSourceFile.fromCode("i" + i, inputs[i]));
}
return modules;
}
private static class BlackHoleErrorManager extends BasicErrorManager {
private BlackHoleErrorManager(Compiler compiler) {
compiler.setErrorManager(this);
}
@Override
public void println(CheckLevel level, JSError error) {}
@Override
public void printSummary() {}
}
Compiler createCompiler() {
Compiler compiler = new Compiler();
return compiler;
}
protected void setExpectedSymbolTableError(DiagnosticType type) {
this.expectedSymbolTableError = type;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.List;
/**
* Peephole optimization to fold constants (e.g. x + 1 + 7 --> x + 8).
*
*/
public class PeepholeFoldConstants extends AbstractPeepholeOptimization {
static final DiagnosticType DIVIDE_BY_0_ERROR = DiagnosticType.error(
"JSC_DIVIDE_BY_0_ERROR",
"Divide by 0");
static final DiagnosticType INVALID_GETELEM_INDEX_ERROR =
DiagnosticType.error(
"JSC_INVALID_GETELEM_INDEX_ERROR",
"Array index not integer: {0}");
static final DiagnosticType INDEX_OUT_OF_BOUNDS_ERROR =
DiagnosticType.error(
"JSC_INDEX_OUT_OF_BOUNDS_ERROR",
"Array index out of bounds: {0}");
static final DiagnosticType NEGATING_A_NON_NUMBER_ERROR =
DiagnosticType.error(
"JSC_NEGATING_A_NON_NUMBER_ERROR",
"Can't negate non-numeric value: {0}");
static final DiagnosticType BITWISE_OPERAND_OUT_OF_RANGE =
DiagnosticType.error(
"JSC_BITWISE_OPERAND_OUT_OF_RANGE",
"Operand out of range, bitwise operation will lose information: {0}");
static final DiagnosticType SHIFT_AMOUNT_OUT_OF_BOUNDS = DiagnosticType.error(
"JSC_SHIFT_AMOUNT_OUT_OF_BOUNDS",
"Shift amount out of bounds: {0}");
static final DiagnosticType FRACTIONAL_BITWISE_OPERAND = DiagnosticType.error(
"JSC_FRACTIONAL_BITWISE_OPERAND",
"Fractional bitwise operand: {0}");
private static final double MAX_FOLD_NUMBER = Math.pow(2, 53);
@Override
Node optimizeSubtree(Node subtree) {
switch(subtree.getType()) {
case
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Token.CALL:
return tryFoldKnownMethods(subtree);
case Token.NEW:
return tryFoldCtorCall(subtree);
case Token.TYPEOF:
return tryFoldTypeof(subtree);
case Token.NOT:
case Token.NEG:
case Token.BITNOT:
return tryFoldUnaryOperator(subtree);
default:
return tryFoldBinaryOperator(subtree);
}
}
private Node tryFoldBinaryOperator(Node subtree) {
Node left = subtree.getFirstChild();
if (left == null) {
return subtree;
}
Node right = left.getNext();
if (right == null) {
return subtree;
}
// If we've reached here, node is truly a binary operator.
switch(subtree.getType()) {
case Token.GETPROP:
return tryFoldGetProp(subtree, left, right);
case Token.GETELEM:
return tryFoldGetElem(subtree, left, right);
case Token.INSTANCEOF:
return tryFoldInstanceof(subtree, left, right);
case Token.AND:
case Token.OR:
return tryFoldAndOr(subtree, left, right);
case Token.LSH:
case Token.RSH:
case Token.URSH:
return tryFoldShift(subtree, left, right);
case Token.ASSIGN:
return tryFoldAssign(subtree, left, right);
case Token.ADD:
return tryFoldAdd(subtree, left, right);
case Token.SUB:
case Token.DIV:
case Token.MOD:
return tryFoldArithmeticOp(subtree, left, right);
case Token.MUL:
case Token.BITAND:
case Token.BITOR:
Node result = tryFoldArithmeticOp(subtree, left, right);
if (result != subtree) {
return result;
}
return tryFoldLeftChildOp(subtree, left, right);
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
return tryFoldComparison(subtree, left, right);
default:
return subtree;
}
}
/**
* Folds 'typeof(foo)' if foo is a literal, e.g.
* typeof("bar") --> "string"
* typeof(6) --> "number"
*/
private Node tryFoldTypeof(Node originalTypeofNode) {
Preconditions.checkArgument(originalTypeofNode.getType() == Token.TYPEOF);
Node argumentNode = originalTypeofNode.getFirstChild();
if (argumentNode == null || !NodeUtil.isLiteralValue(argumentNode, true)) {
return originalTypeofNode;
}
String typeNameString = null;
switch (argumentNode.getType()) {
case Token.FUNCTION:
typeNameString = "function";
break;
case Token.STRING:
typeNameString =
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> "string";
break;
case Token.NUMBER:
typeNameString = "number";
break;
case Token.TRUE:
case Token.FALSE:
typeNameString = "boolean";
break;
case Token.NULL:
case Token.OBJECTLIT:
case Token.ARRAYLIT:
typeNameString = "object";
break;
case Token.VOID:
typeNameString = "undefined";
break;
case Token.NAME:
// We assume here that programs don't change the value of the
// keyword undefined to something other than the value undefined.
if ("undefined".equals(argumentNode.getString())) {
typeNameString = "undefined";
}
break;
}
if (typeNameString != null) {
Node newNode = Node.newString(typeNameString);
originalTypeofNode.getParent().replaceChild(originalTypeofNode, newNode);
reportCodeChange();
return newNode;
}
return originalTypeofNode;
}
private Node tryFoldUnaryOperator(Node n) {
Preconditions.checkState(n.hasOneChild());
Node left = n.getFirstChild();
Node parent = n.getParent();
if (left == null) {
return n;
}
TernaryValue leftVal = NodeUtil.getBooleanValue(left);
if (leftVal == TernaryValue.UNKNOWN) {
return n;
}
switch (n.getType()) {
case Token.NOT:
int result = leftVal.toBoolean(true) ? Token.FALSE : Token.TRUE;
Node replacementNode = new Node(result);
parent.replaceChild(n, replacementNode);
reportCodeChange();
return replacementNode;
case Token.NEG:
try {
if (left.getType() == Token.NAME) {
if (left.getString().equals("Infinity")) {
// "-Infinity" is valid and a literal, don't modify it.
return n;
} else if (left.getString().equals("NaN")) {
// "-NaN" is "NaN".
n.removeChild(left);
parent.replaceChild(n, left);
reportCodeChange();
return left;
}
}
double negNum = -left.getDouble();
Node negNumNode = Node.newNumber(negNum);
parent.replaceChild(n, negNumNode);
reportCodeChange();
return negNumNode;
} catch (UnsupportedOperationException ex) {
// left is not a number node, so do not replace, but warn the
// user because they can't be doing anything good
error(NEGATING_A_NON_NUMBER_ERROR, left);
return n;
}
case Token.BITNOT:
try {
double val = left.getDouble();
if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) {
int intVal = (int) val;
if (intVal == val) {
Node notIntValNode = Node.newNumber(~intVal);
parent.replaceChild(n, notIntValNode);
reportCodeChange();
return not
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>IntValNode;
} else {
error(FRACTIONAL_BITWISE_OPERAND, left);
return n;
}
} else {
error(BITWISE_OPERAND_OUT_OF_RANGE, left);
return n;
}
} catch (UnsupportedOperationException ex) {
// left is not a number node, so do not replace, but warn the
// user because they can't be doing anything good
error(NEGATING_A_NON_NUMBER_ERROR, left);
return n;
}
default:
return n;
}
}
/**
* Try to fold {@code left instanceof right} into {@code true}
* or {@code false}.
*/
private Node tryFoldInstanceof(Node n, Node left, Node right) {
Preconditions.checkArgument(n.getType() == Token.INSTANCEOF);
// TODO(johnlenz) Use type information if available to fold
// instanceof.
if (NodeUtil.isLiteralValue(left, true)
&& !mayHaveSideEffects(right)) {
Node replacementNode = null;
if (NodeUtil.isImmutableValue(left)) {
// Non-object types are never instances.
replacementNode = new Node(Token.FALSE);
} else if (right.getType() == Token.NAME
&& "Object".equals(right.getString())) {
replacementNode = new Node(Token.TRUE);
}
if (replacementNode != null) {
n.getParent().replaceChild(n, replacementNode);
reportCodeChange();
return replacementNode;
}
}
return n;
}
private Node tryFoldAssign(Node n, Node left, Node right) {
Preconditions.checkArgument(n.getType() == Token.ASSIGN);
// Tries to convert x = x + y -> x += y;
if (!right.hasChildren() ||
right.getFirstChild().getNext() != right.getLastChild()) {
// RHS must have two children.
return n;
}
if (mayHaveSideEffects(left)) {
return n;
}
Node leftChild = right.getFirstChild();
if (!areNodesEqualForInlining(left, leftChild)) {
return n;
}
int newType = -1;
switch (right.getType()) {
case Token.ADD:
newType = Token.ASSIGN_ADD;
break;
case Token.BITAND:
newType = Token.ASSIGN_BITAND;
break;
case Token.BITOR:
newType = Token.ASSIGN_BITOR;
break;
case Token.BITXOR:
newType = Token.ASSIGN_BITXOR;
break;
case Token.DIV:
newType = Token.ASSIGN_DIV;
break;
case Token.LSH:
newType = Token.ASSIGN_LSH;
break;
case Token.MOD:
newType = Token.ASSIGN_MOD;
break;
case Token.MUL:
newType = Token.ASSIGN_MUL;
break;
case Token.RSH:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
newType = Token.ASSIGN_RSH;
break;
case Token.SUB:
newType = Token.ASSIGN_SUB;
break;
case Token.URSH:
newType = Token.ASSIGN_URSH;
break;
default:
return n;
}
Node newNode = new Node(newType,
left.detachFromParent(), right.getLastChild().detachFromParent());
n.getParent().replaceChild(n, newNode);
reportCodeChange();
return newNode;
}
/**
* Try to fold a AND/OR node.
*/
private Node tryFoldAndOr(Node n, Node left, Node right) {
Node parent = n.getParent();
Node result = null;
int type = n.getType();
TernaryValue leftVal = NodeUtil.getBooleanValue(left);
if (leftVal != TernaryValue.UNKNOWN) {
boolean lval = leftVal.toBoolean(true);
// (TRUE || x) => TRUE (also, (3 || x) => 3)
// (FALSE && x) => FALSE
if (lval && type == Token.OR ||
!lval && type == Token.AND) {
result = left;
} else {
// (FALSE || x) => x
// (TRUE && x) => x
result = right;
}
}
// Note: Right hand side folding is handled by
// PeepholeSubstituteAlternateSyntax#tryMinimizeCondition
if (result != null) {
// Fold it!
n.removeChild(result);
parent.replaceChild(n, result);
reportCodeChange();
return result;
} else {
return n;
}
}
/**
* Expressions such as [foo() + 'a' + 'b'] generate parse trees
* where no node has two const children ((foo() + 'a') + 'b'), so
* tryFoldAdd() won't fold it -- tryFoldLeftChildAdd() will (for Strings).
* Specifically it folds Add exprssions where:
* - The left child is also and add expression
* - The right child is a constant value
* - The left child's right child is a STRING constant.
*/
private Node tryFoldLeftChildAdd(Node n, Node left, Node right) {
if (NodeUtil.isLiteralValue(right, false) &&
left.getType() == Token.ADD &&
left.getChildCount() == 2) {
Node ll = left.getFirstChild();
Node lr = ll.getNext();
// Left's right child MUST be a string. We would not want to fold
// foo() + 2 + 'a' because we don't know what foo() will return, and
// therefore we don't know if left is a string concat, or a numeric add.
if (lr.getType() != Token.STRING) {
return n;
}
String leftString = NodeUtil.getStringValue(lr);
String rightString = NodeUtil.getStringValue(right
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>);
if (leftString != null && rightString != null) {
left.removeChild(ll);
String result = leftString + rightString;
n.replaceChild(left, ll);
n.replaceChild(right, Node.newString(result));
reportCodeChange();
}
}
return n;
}
/**
* Try to fold an ADD node with constant operands
*/
private Node tryFoldAddConstant(Node n, Node left, Node right) {
if (left.getType() == Token.STRING ||
right.getType() == Token.STRING) {
// Add strings.
String leftString = NodeUtil.getStringValue(left);
String rightString = NodeUtil.getStringValue(right);
if (leftString != null && rightString != null) {
Node newStringNode = Node.newString(leftString + rightString);
n.getParent().replaceChild(n, newStringNode);
reportCodeChange();
return newStringNode;
}
} else {
// Try arithmetic add
return tryFoldArithmeticOp(n, left, right);
}
return n;
}
/**
* Try to fold arithmetic binary operators
*/
private Node tryFoldArithmeticOp(Node n, Node left, Node right) {
Node result = performArithmeticOp(n.getType(), left, right);
if (result != null) {
n.getParent().replaceChild(n, result);
reportCodeChange();
return result;
}
return n;
}
/**
* Try to fold arithmetic binary operators
*/
private Node performArithmeticOp(int opType, Node left, Node right) {
// Unlike other operations, ADD operands are not always converted
// to Number.
if (opType == Token.ADD
&& (left.getType() != Token.NUMBER
|| right.getType() != Token.NUMBER)) {
return null;
}
double result;
Double lValObj = NodeUtil.getNumberValue(left);
if (lValObj == null) {
return null;
}
Double rValObj = NodeUtil.getNumberValue(right);
if (rValObj == null) {
return null;
}
double lval = lValObj;
double rval = rValObj;
switch (opType) {
case Token.BITAND:
if (!areValidInts(lval, rval)) {
return null;
}
result = (int)lval & (int)rval;
break;
case Token.BITOR:
if (!areValidInts(lval, rval)) {
return null;
}
result = (int)lval | (int)rval;
break;
case Token.ADD:
result = lval + rval;
break;
case Token.SUB:
result = lval - rval;
break;
case Token.MUL:
result = lval * rval;
break;
case Token.MOD:
if (rval == 0) {
error(DIVIDE_BY_0
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>_ERROR, right);
return null;
}
result = lval % rval;
break;
case Token.DIV:
if (rval == 0) {
error(DIVIDE_BY_0_ERROR, right);
return null;
}
result = lval / rval;
break;
default:
throw new Error("Unexpected arithmetic operator");
}
// TODO(johnlenz): consider removing the result length check.
// length of the left and right value plus 1 byte for the operator.
if (String.valueOf(result).length() <=
String.valueOf(lval).length() + String.valueOf(rval).length() + 1 &&
// Do not try to fold arithmetic for numbers > 2^53. After that
// point, fixed-point math starts to break down and become inaccurate.
Math.abs(result) <= MAX_FOLD_NUMBER) {
Node newNumber = Node.newNumber(result);
return newNumber;
} else if (Double.isNaN(result)) {
return Node.newString(Token.NAME, "NaN");
} else if (result == Double.POSITIVE_INFINITY) {
return Node.newString(Token.NAME, "Infinity");
} else if (result == Double.NEGATIVE_INFINITY) {
return new Node(Token.NEG, Node.newString(Token.NAME, "Infinity"));
}
return null;
}
/**
* @return Whether the double can be precisely represented as a int.
*/
private boolean isValidInt(double val) {
return !(val < Integer.MIN_VALUE || val > Integer.MAX_VALUE)
&& val == (int)val;
}
/**
* @return Whether the parameters are doubles can be precisely represented
* as a int.
*/
private boolean areValidInts(double val1, double val2) {
return isValidInt(val1) && isValidInt(val2);
}
/**
* Expressions such as [foo() * 10 * 20] generate parse trees
* where no node has two const children ((foo() * 10) * 20), so
* performArithmeticOp() won't fold it -- tryFoldLeftChildOp() will.
* Specifically it folds associative expressions where:
* - The left child is also an associative expression of the same time.
* - The right child is a constant NUMBER constant.
* - The left child's right child is a NUMBER constant.
*/
private Node tryFoldLeftChildOp(Node n, Node left, Node right) {
int opType = n.getType();
// Note: ADD is not associative when used as a string concat operator.
Preconditions.checkState(
NodeUtil.isAssociative(opType) && NodeUtil.isCommutative(opType));
// TODO(johnlenz): create and use a getNumberValue.
if (right.getType() == Token.NUMBER && left.getType() == op
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Type) {
Preconditions.checkState(left.getChildCount() == 2);
Node ll = left.getFirstChild();
Node lr = ll.getNext();
Node valueToCombine;
if (ll.getType() == Token.NUMBER) {
valueToCombine = ll;
} else if (lr.getType() == Token.NUMBER) {
valueToCombine = lr;
} else {
// Nothing to do.
return n;
}
Node replacement = performArithmeticOp(opType, valueToCombine, right);
if (replacement != null) {
left.removeChild(valueToCombine);
n.replaceChild(left, left.removeFirstChild());
n.replaceChild(right, replacement);
reportCodeChange();
}
}
return n;
}
private Node tryFoldAdd(Node node, Node left, Node right) {
Preconditions.checkArgument(node.getType() == Token.ADD);
if (NodeUtil.isLiteralValue(left, false) &&
NodeUtil.isLiteralValue(right, false)) {
// 6 + 7
return tryFoldAddConstant(node, left, right);
} else {
// a + 7 or 6 + a
return tryFoldLeftChildAdd(node, left, right);
}
}
/**
* Try to fold shift operations
*/
private Node tryFoldShift(Node n, Node left, Node right) {
if (left.getType() == Token.NUMBER &&
right.getType() == Token.NUMBER) {
double result;
double lval = left.getDouble();
double rval = right.getDouble();
// check ranges. We do not do anything that would clip the double to
// a 32-bit range, since the user likely does not intend that.
if (!(lval >= Integer.MIN_VALUE && lval <= Integer.MAX_VALUE)) {
error(BITWISE_OPERAND_OUT_OF_RANGE, left);
return n;
}
// only the lower 5 bits are used when shifting, so don't do anything
// if the shift amount is outside [0,32)
if (!(rval >= 0 && rval < 32)) {
error(SHIFT_AMOUNT_OUT_OF_BOUNDS, right);
return n;
}
// Convert the numbers to ints
int lvalInt = (int) lval;
if (lvalInt != lval) {
error(FRACTIONAL_BITWISE_OPERAND, left);
return n;
}
int rvalInt = (int) rval;
if (rvalInt != rval) {
error(FRACTIONAL_BITWISE_OPERAND, right);
return n;
}
switch (n.getType()) {
case Token.LSH:
result = lvalInt << rvalInt;
break;
case Token.RSH:
result = lvalInt >> rvalInt;
break;
case Token.URSH:
// JavaScript handles zero shifts on signed numbers differently than
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> // Java as an Java int can not represent the unsigned 32-bit number
// where JavaScript can so use a long here.
long lvalLong = lvalInt & 0xffffffffL;
result = lvalLong >>> rvalInt;
break;
default:
throw new AssertionError("Unknown shift operator: " +
Node.tokenToName(n.getType()));
}
Node newNumber = Node.newNumber(result);
n.getParent().replaceChild(n, newNumber);
reportCodeChange();
return newNumber;
}
return n;
}
/**
* Try to fold comparison nodes, e.g ==
*/
@SuppressWarnings("fallthrough")
private Node tryFoldComparison(Node n, Node left, Node right) {
if (!NodeUtil.isLiteralValue(left, false) ||
!NodeUtil.isLiteralValue(right, false)) {
// We only handle non-literal operands for LT and GT.
if (n.getType() != Token.GT && n.getType() != Token.LT) {
return n;
}
}
int op = n.getType();
boolean result;
// TODO(johnlenz): Use the JSType to compare nodes of different types.
boolean rightLiteral = NodeUtil.isLiteralValue(right, false);
boolean undefinedRight = ((Token.NAME == right.getType()
&& right.getString().equals("undefined"))
|| (Token.VOID == right.getType()
&& NodeUtil.isLiteralValue(right.getFirstChild(), false)));
switch (left.getType()) {
case Token.VOID:
if (!NodeUtil.isLiteralValue(left.getFirstChild(), false)) {
return n;
} else if (!rightLiteral) {
return n;
} else {
result = compareToUndefined(right, op);
}
break;
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
if (undefinedRight) {
result = compareToUndefined(left, op);
break;
}
int rhType = right.getType();
if (rhType != Token.TRUE &&
rhType != Token.FALSE &&
rhType != Token.NULL) {
return n;
}
switch (op) {
case Token.SHEQ:
case Token.EQ:
result = left.getType() == right.getType();
break;
case Token.SHNE:
case Token.NE:
result = left.getType() != right.getType();
break;
case Token.GE:
case Token.LE:
case Token.GT:
case Token.LT:
Boolean compareResult = compareAsNumbers(op, left, right);
if (compareResult != null) {
result = compareResult;
} else {
return n;
}
break;
default:
return n; // we only handle == and != here
}
break;
case Token.THIS:
if (right.getType() != Token.THIS) {
return n;
}
switch (op)
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {
case Token.SHEQ:
case Token.EQ:
result = true;
break;
case Token.SHNE:
case Token.NE:
result = false;
break;
// We can only handle == and != here.
// GT, LT, GE, LE depend on the type of "this" and how it will
// be converted to number. The results are different depending on
// whether it is a string, NaN or other number value.
default:
return n;
}
break;
case Token.STRING:
if (undefinedRight) {
result = compareToUndefined(left, op);
break;
}
if (Token.STRING != right.getType()) {
return n; // Only eval if they are the same type
}
switch (op) {
case Token.SHEQ:
case Token.EQ:
result = left.getString().equals(right.getString());
break;
case Token.SHNE:
case Token.NE:
result = !left.getString().equals(right.getString());
break;
default:
return n; // we only handle == and != here
}
break;
case Token.NUMBER:
if (undefinedRight) {
result = compareToUndefined(left, op);
break;
}
if (Token.NUMBER != right.getType()) {
return n; // Only eval if they are the same type
}
Boolean compareResult = compareAsNumbers(op, left, right);
if (compareResult != null) {
result = compareResult;
} else {
return null;
}
break;
case Token.NAME:
if (undefinedRight) {
result = compareToUndefined(left, op);
break;
}
if (rightLiteral) {
boolean undefinedLeft = (left.getString().equals("undefined"));
if (undefinedLeft) {
result = compareToUndefined(right, op);
break;
}
}
if (Token.NAME != right.getType()) {
return n; // Only eval if they are the same type
}
String ln = left.getString();
String rn = right.getString();
if (!ln.equals(rn)) {
return n; // Not the same value name.
}
switch (op) {
// If we knew the named value wouldn't be NaN, it would be nice
// to handle EQ,NE,LE,GE,SHEQ, and SHNE.
case Token.LT:
case Token.GT:
result = false;
break;
default:
return n; // don't handle that op
}
break;
default:
// assert, this should cover all consts
return n;
}
Node newNode = new Node(result ? Token.TRUE : Token.FALSE);
n.getParent().replaceChild(n, newNode);
reportCodeChange();
return newNode;
}
/**
* The result of the comparison as a Boolean or null if the
* result could not be determined.
*/
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> private Boolean compareAsNumbers(int op, Node left, Node right) {
Double leftValue = NodeUtil.getNumberValue(left);
if (leftValue == null) {
return null;
}
Double rightValue = NodeUtil.getNumberValue(right);
if (rightValue == null) {
return null;
}
double lv = leftValue;
double rv = rightValue;
Boolean result;
switch (op) {
case Token.SHEQ:
case Token.EQ:
Preconditions.checkState(
left.getType() == Token.NUMBER && right.getType() == Token.NUMBER);
result = lv == rv;
break;
case Token.SHNE:
case Token.NE:
Preconditions.checkState(
left.getType() == Token.NUMBER && right.getType() == Token.NUMBER);
result = lv != rv;
break;
case Token.LE: result = lv <= rv; break;
case Token.LT: result = lv < rv; break;
case Token.GE: result = lv >= rv; break;
case Token.GT: result = lv > rv; break;
default:
return null; // don't handle that op
}
return result;
}
/**
* @param value The value to compare to "undefined"
* @param op The boolean op to compare with
* @return Whether the boolean op is true or false
*/
private boolean compareToUndefined(Node value, int op) {
boolean valueUndefined = ((Token.NAME == value.getType()
&& value.getString().equals("undefined"))
|| (Token.VOID == value.getType()
&& NodeUtil.isLiteralValue(value.getFirstChild(), false)));
boolean valueNull = (Token.NULL == value.getType());
boolean equivalent = valueUndefined || valueNull;
switch (op) {
case Token.EQ:
// undefined is only equal to null or an undefined value
return equivalent;
case Token.NE:
return !equivalent;
case Token.SHEQ:
return valueUndefined;
case Token.SHNE:
return !valueUndefined;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
return false;
default:
throw new IllegalStateException("unexpected.");
}
}
/**
* Try to fold away unnecessary object instantiation.
* e.g. this[new String('eval')] -> this.eval
*/
private Node tryFoldCtorCall(Node n) {
Preconditions.checkArgument(n.getType() == Token.NEW);
// we can remove this for GETELEM calls (anywhere else?)
if (inForcedStringContext(n)) {
return tryFoldInForcedStringContext(n);
}
return n;
}
/** Returns whether this node must be coerced to a string. */
private boolean inForcedStringContext(Node n) {
return n.getParent().getType() == Token.GETELEM &&
n.getParent().getLastChild() == n;
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
private Node tryFoldInForcedStringContext(Node n) {
// For now, we only know how to fold ctors.
Preconditions.checkArgument(n.getType() == Token.NEW);
Node objectType = n.getFirstChild();
if (objectType.getType() != Token.NAME) {
return n;
}
if (objectType.getString().equals("String")) {
Node value = objectType.getNext();
String stringValue = null;
if (value == null) {
stringValue = "";
} else {
if (!NodeUtil.isImmutableValue(value)) {
return n;
}
stringValue = NodeUtil.getStringValue(value);
}
if (stringValue == null) {
return n;
}
Node parent = n.getParent();
Node newString = Node.newString(stringValue);
parent.replaceChild(n, newString);
newString.copyInformationFrom(parent);
reportCodeChange();
return newString;
}
return n;
}
private Node tryFoldKnownMethods(Node subtree) {
// For now we only support .join(),
// .indexOf(), .substring() and .substr()
subtree = tryFoldArrayJoin(subtree);
if (subtree.getType() == Token.CALL) {
subtree = tryFoldKnownStringMethods(subtree);
}
return subtree;
}
/**
* Try to eveluate known String methods
* .indexOf(), .substr(), .substring()
*/
private Node tryFoldKnownStringMethods(Node subtree) {
Preconditions.checkArgument(subtree.getType() == Token.CALL);
// check if this is a call on a string method
// then dispatch to specific folding method.
Node callTarget = subtree.getFirstChild();
if (callTarget == null) {
return subtree;
}
Node firstArg = callTarget.getNext();
if (firstArg == null) {
return subtree;
}
if (!NodeUtil.isGet(callTarget) ||
!NodeUtil.isImmutableValue(firstArg)) {
return subtree;
}
Node stringNode = callTarget.getFirstChild();
Node functionName = stringNode.getNext();
if ((stringNode.getType() != Token.STRING) || (
(functionName.getType() != Token.STRING))) {
return subtree;
}
String functionNameString = functionName.getString();
if (functionNameString.equals("indexOf") ||
functionNameString.equals("lastIndexOf")) {
subtree = tryFoldStringIndexOf(subtree, functionNameString,
stringNode, firstArg);
} else if (functionNameString.equals("substr")) {
subtree = tryFoldStringSubstr(subtree, stringNode, firstArg);
} else if (functionNameString.equals("substring")) {
subtree = tryFoldStringSubstring(subtree, stringNode, firstArg);
}
return subtree;
}
/**
* Try to evaluate String.indexOf/lastIndexOf:
* "abcdef".indexOf("bc") ->
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> 1
* "abcdefbc".indexOf("bc", 3) -> 6
*/
private Node tryFoldStringIndexOf(
Node n, String functionName, Node lstringNode, Node firstArg) {
Preconditions.checkArgument(n.getType() == Token.CALL);
Preconditions.checkArgument(lstringNode.getType() == Token.STRING);
String lstring = NodeUtil.getStringValue(lstringNode);
boolean isIndexOf = functionName.equals("indexOf");
Node secondArg = firstArg.getNext();
String searchValue = NodeUtil.getStringValue(firstArg);
// searchValue must be a valid string.
if (searchValue == null) {
return n;
}
int fromIndex = isIndexOf ? 0 : lstring.length();
if (secondArg != null) {
// Third-argument and non-numeric second arg are problematic. Discard.
if ((secondArg.getNext() != null) ||
(secondArg.getType() != Token.NUMBER)) {
return n;
} else {
fromIndex = (int) secondArg.getDouble();
}
}
int indexVal = isIndexOf ? lstring.indexOf(searchValue, fromIndex)
: lstring.lastIndexOf(searchValue, fromIndex);
Node newNode = Node.newNumber(indexVal);
n.getParent().replaceChild(n, newNode);
reportCodeChange();
return newNode;
}
/**
* Try to fold an array join: ['a', 'b', 'c'].join('') -> 'abc';
*/
private Node tryFoldArrayJoin(Node n) {
Node callTarget = n.getFirstChild();
if (callTarget == null) {
return n;
}
Node right = callTarget.getNext();
if (right == null) {
return n;
}
if (!NodeUtil.isGetProp(callTarget) || !NodeUtil.isImmutableValue(right)) {
return n;
}
Node arrayNode = callTarget.getFirstChild();
Node functionName = arrayNode.getNext();
if ((arrayNode.getType() != Token.ARRAYLIT) ||
!functionName.getString().equals("join")) {
return n;
}
String joinString = NodeUtil.getStringValue(right);
List<Node> arrayFoldedChildren = Lists.newLinkedList();
StringBuilder sb = null;
int foldedSize = 0;
Node prev = null;
Node elem = arrayNode.getFirstChild();
// Merges adjacent String nodes.
while (elem != null) {
if (NodeUtil.isImmutableValue(elem)) {
if (sb == null) {
sb = new StringBuilder();
} else {
sb.append(joinString);
}
sb.append(NodeUtil.getStringValue(elem));
} else {
if (sb != null) {
Preconditions.checkNotNull(prev);
// + 2 for the quotes.
foldedSize += sb.length() + 2;
arrayFoldedChildren.add(
Node.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>newString(sb.toString()).copyInformationFrom(prev));
sb = null;
}
foldedSize += InlineCostEstimator.getCost(elem);
arrayFoldedChildren.add(elem);
}
prev = elem;
elem = elem.getNext();
}
if (sb != null) {
Preconditions.checkNotNull(prev);
// + 2 for the quotes.
foldedSize += sb.length() + 2;
arrayFoldedChildren.add(
Node.newString(sb.toString()).copyInformationFrom(prev));
}
// one for each comma.
foldedSize += arrayFoldedChildren.size() - 1;
int originalSize = InlineCostEstimator.getCost(n);
switch (arrayFoldedChildren.size()) {
case 0:
Node emptyStringNode = Node.newString("");
n.getParent().replaceChild(n, emptyStringNode);
reportCodeChange();
return emptyStringNode;
case 1:
Node foldedStringNode = arrayFoldedChildren.remove(0);
if (foldedSize > originalSize) {
return n;
}
arrayNode.detachChildren();
if (foldedStringNode.getType() != Token.STRING) {
// If the Node is not a string literal, ensure that
// it is coerced to a string.
Node replacement = new Node(Token.ADD,
Node.newString("").copyInformationFrom(right),
foldedStringNode);
foldedStringNode = replacement;
}
n.getParent().replaceChild(n, foldedStringNode);
reportCodeChange();
return foldedStringNode;
default:
// No folding could actually be performed.
if (arrayFoldedChildren.size() == arrayNode.getChildCount()) {
return n;
}
int kJoinOverhead = "[].join()".length();
foldedSize += kJoinOverhead;
foldedSize += InlineCostEstimator.getCost(right);
if (foldedSize > originalSize) {
return n;
}
arrayNode.detachChildren();
for (Node node : arrayFoldedChildren) {
arrayNode.addChildToBack(node);
}
reportCodeChange();
break;
}
return n;
}
/**
* Try to fold .substr() calls on strings
*/
private Node tryFoldStringSubstr(Node n, Node stringNode, Node arg1) {
Preconditions.checkArgument(n.getType() == Token.CALL);
Preconditions.checkArgument(stringNode.getType() == Token.STRING);
int start, length;
String stringAsString = stringNode.getString();
// TODO(nicksantos): We really need a NodeUtil.getNumberValue
// function.
if (arg1 != null && arg1.getType() == Token.NUMBER) {
start = (int) arg1.getDouble();
} else {
return n;
}
Node arg2 = arg1.getNext();
if (arg
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>2 != null) {
if (arg2.getType() == Token.NUMBER) {
length = (int) arg2.getDouble();
} else {
return n;
}
if (arg2.getNext() != null) {
// If we got more args than we expected, bail out.
return n;
}
} else {
// parameter 2 not passed
length = stringAsString.length() - start;
}
// Don't handle these cases. The specification actually does
// specify the behavior in some of these cases, but we haven't
// done a thorough investigation that it is correctly implemented
// in all browsers.
if ((start + length) > stringAsString.length() ||
(length < 0) ||
(start < 0)) {
return n;
}
String result = stringAsString.substring(start, start + length);
Node resultNode = Node.newString(result);
Node parent = n.getParent();
parent.replaceChild(n, resultNode);
reportCodeChange();
return resultNode;
}
/**
* Try to fold .substring() calls on strings
*/
private Node tryFoldStringSubstring(Node n, Node stringNode, Node arg1) {
Preconditions.checkArgument(n.getType() == Token.CALL);
Preconditions.checkArgument(stringNode.getType() == Token.STRING);
int start, end;
String stringAsString = stringNode.getString();
if (arg1 != null && arg1.getType() == Token.NUMBER) {
start = (int) arg1.getDouble();
} else {
return n;
}
Node arg2 = arg1.getNext();
if (arg2 != null) {
if (arg2.getType() == Token.NUMBER) {
end = (int) arg2.getDouble();
} else {
return n;
}
if (arg2.getNext() != null) {
// If we got more args than we expected, bail out.
return n;
}
} else {
// parameter 2 not passed
end = stringAsString.length();
}
// Don't handle these cases. The specification actually does
// specify the behavior in some of these cases, but we haven't
// done a thorough investigation that it is correctly implemented
// in all browsers.
if ((end > stringAsString.length()) ||
(start > stringAsString.length()) ||
(end < 0) ||
(start < 0)) {
return n;
}
String result = stringAsString.substring(start, end);
Node resultNode = Node.newString(result);
Node parent = n.getParent();
parent.replaceChild(n, resultNode);
reportCodeChange();
return resultNode;
}
/**
* Try to fold array-element. e.g [1, 2, 3][10];
*/
private Node tryFoldGetElem(Node n, Node left, Node right) {
Preconditions.checkArgument(n.getType() == Token.GETELEM);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> if (left.getType() == Token.ARRAYLIT) {
if (right.getType() != Token.NUMBER) {
// Sometimes people like to use complex expressions to index into
// arrays, or strings to index into array methods.
return n;
}
double index = right.getDouble();
int intIndex = (int) index;
if (intIndex != index) {
error(INVALID_GETELEM_INDEX_ERROR, right);
return n;
}
if (intIndex < 0) {
error(INDEX_OUT_OF_BOUNDS_ERROR, right);
return n;
}
Node elem = left.getFirstChild();
for (int i = 0; elem != null && i < intIndex; i++) {
elem = elem.getNext();
}
if (elem == null) {
error(INDEX_OUT_OF_BOUNDS_ERROR, right);
return n;
}
// Replace the entire GETELEM with the value
left.removeChild(elem);
n.getParent().replaceChild(n, elem);
reportCodeChange();
return elem;
}
return n;
}
/**
* Try to fold array-length. e.g [1, 2, 3].length ==> 3, [x, y].length ==> 2
*/
private Node tryFoldGetProp(Node n, Node left, Node right) {
Preconditions.checkArgument(n.getType() == Token.GETPROP);
if (right.getType() == Token.STRING &&
right.getString().equals("length")) {
int knownLength = -1;
switch (left.getType()) {
case Token.ARRAYLIT:
if (mayHaveSideEffects(left)) {
// Nope, can't fold this, without handling the side-effects.
return n;
}
knownLength = left.getChildCount();
break;
case Token.STRING:
knownLength = left.getString().length();
break;
default:
// Not a foldable case, forget it.
return n;
}
Preconditions.checkState(knownLength != -1);
Node lengthNode = Node.newNumber(knownLength);
n.getParent().replaceChild(n, lengthNode);
reportCodeChange();
return lengthNode;
}
return n;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> the content of A is "upward exposed"
* at point N_4 and N_5.
*
* Example:
*
* A = 1;
* ...
* N_3:
* N_4: print(A);
* N_5: y = A;
* N_6: A = 1;
* N_7: print(A);
*
* At N_3, reads of A in {N_4, N_5} are said to be upward exposed.
*/
static final class ReachingUses implements LatticeElement {
final Multimap<Var, Node> mayUseMap;
public ReachingUses() {
mayUseMap = HashMultimap.create();
}
/**
* Copy constructor.
*
* @param other The constructed object is a replicated copy of this element.
*/
public ReachingUses(ReachingUses other) {
mayUseMap = HashMultimap.create(other.mayUseMap);
}
@Override
public boolean equals(Object other) {
return (other instanceof ReachingUses) &&
((ReachingUses) other).mayUseMap.equals(this.mayUseMap);
}
@Override
public int hashCode() {
return mayUseMap.hashCode();
}
}
/**
* The join is a simple union because of the "may be" nature of the analysis.
*
* Consider: A = 1; if (x) { A = 2 }; alert(A);
*
* The read of A "may be" exposed to A = 1 in the beginning.
*/
private static class ReachingUsesJoinOp implements JoinOp<ReachingUses> {
@Override
public ReachingUses apply(List<ReachingUses> from) {
ReachingUses result = new ReachingUses();
for (ReachingUses uses : from) {
result.mayUseMap.putAll(uses.mayUseMap);
}
return result;
}
}
@Override
boolean isForward() {
return false;
}
@Override
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
computeMayUse(n, n, output, false);
return output;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
case Token.DO:
case Token.IF:
computeMayUse(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
return;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> case Token.FOR:
if (!NodeUtil.isForIn(n)) {
computeMayUse(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
} else {
// for(x in y) {...}
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (NodeUtil.isVar(lhs)) {
lhs = lhs.getLastChild(); // for(var x in y) {...}
}
if (NodeUtil.isName(lhs) && !conditional) {
removeFromUseIfLocal(lhs.getString(), output);
}
computeMayUse(rhs, cfgNode, output, conditional);
}
return;
case Token.AND:
case Token.OR:
computeMayUse(n.getLastChild(), cfgNode, output, true);
computeMayUse(n.getFirstChild(), cfgNode, output, conditional);
return;
case Token.HOOK:
computeMayUse(n.getLastChild(), cfgNode, output, true);
computeMayUse(n.getFirstChild().getNext(), cfgNode, output, true);
computeMayUse(n.getFirstChild(), cfgNode, output, conditional);
return;
case Token.VAR:
Node varName = n.getFirstChild();
Preconditions.checkState(n.hasChildren(), "AST should be normalized");
if (varName.hasChildren()) {
computeMayUse(varName.getFirstChild(), cfgNode, output, conditional);
if (!conditional) {
removeFromUseIfLocal(varName.getString(), output);
}
}
return;
default:
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isName(n.getFirstChild())) {
Node name = n.getFirstChild();
if (!conditional) {
removeFromUseIfLocal(name.getString(), output);
}
// In case of a += "Hello". There is a read of a.
if (!NodeUtil.isAssign(n)) {
addToUseIfLocal(name.getString(), cfgNode, output);
}
computeMayUse(name.getNext(), cfgNode, output, conditional);
} else {
/*
* We want to traverse in reverse order because we want the LAST
* definition in the sub-tree....
* But we have no better way to traverse in reverse other :'(
*/
for (Node c = n.getLastChild(); c != null; c = n.getChildBefore(c)) {
computeMayUse(c, cfgNode, output, conditional);
}
}
}
}
/**
* Sets the variable for the given name to the node value in the upward
* exposed lattice. Do nothing if the variable name is one of the escaped
* variable.
*/
private void addToUseIfLocal(String name, Node node, ReachingUses use) {
Var var = jsScope.getVar(name);
if (var == null || var.scope != jsScope) {
return;
}
if (!escaped.contains(var)) {
use.mayUse
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Map.put(var, node);
}
}
/**
* Removes the variable for the given name from the node value in the upward
* exposed lattice. Do nothing if the variable name is one of the escaped
* variable.
*/
private void removeFromUseIfLocal(String name, ReachingUses use) {
Var var = jsScope.getVar(name);
if (var == null || var.scope != jsScope) {
return;
}
if (!escaped.contains(var)) {
use.mayUseMap.removeAll(var);
}
}
/**
* Gets a list of nodes that may be using the value assigned to {@code name}
* in {@code defNode}. {@code defNode} must be one of the control flow graph
* nodes.
*
* @param name name of the variable. It can only be names of local variable
* that are not function parameters, escaped variables or variables
* declared in catch.
* @param defNode The list of upward exposed use for the variable.
*/
Collection<Node> getUses(String name, Node defNode) {
GraphNode<Node, Branch> n = getCfg().getNode(defNode);
Preconditions.checkNotNull(n);
FlowState<ReachingUses> state = n.getAnnotation();
return state.getOut().mayUseMap.get(jsScope.getVar(name));
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> like
* {@code @return} the bus.
* and they clearly don't mean that "the" is a type. In these cases, we're
* forgiving and try to guess whether or not "the" is a type when it's not
* clear.
*/
private boolean forgiving = false;
/**
* Create a named type based on the reference.
*/
UnresolvedTypeExpression(JSTypeRegistry registry, Node typeExpr,
String sourceName, boolean forgiving) {
super(registry, false);
Preconditions.checkNotNull(typeExpr);
this.typeExpr = typeExpr;
this.sourceName = sourceName;
this.forgiving = forgiving;
}
/**
* Resolve the referenced type within the enclosing scope.
*/
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) {
return registry.createFromTypeNodes(typeExpr, sourceName, enclosing,
forgiving);
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> input.getModule();
Preconditions.checkNotNull(module);
entryPointInputsPerModule.put(module, input);
}
// Clear the modules of their inputs. This also nulls out
// the input's reference to its module.
for (JSModule module : getAllModules()) {
module.removeAll();
}
// Figure out which sources *must* be in each module, or in one
// of that module's dependencies.
for (JSModule module : entryPointInputsPerModule.keySet()) {
List<CompilerInput> transitiveClosure =
sorter.getSortedDependenciesOf(
entryPointInputsPerModule.get(module));
for (CompilerInput input : transitiveClosure) {
JSModule oldModule = input.getModule();
if (oldModule == null) {
input.setModule(module);
} else {
input.setModule(null);
input.setModule(
getDeepestCommonDependencyInclusive(oldModule, module));
}
}
}
// All the inputs are pointing to the modules that own them. Yeah!
// Update the modules to reflect this.
for (CompilerInput input : absoluteOrder) {
JSModule module = input.getModule();
if (module != null) {
module.add(input);
}
}
// Now, generate the sorted result.
List<CompilerInput> result = Lists.newArrayList();
for (JSModule module : getAllModulesInDependencyOrder()) {
result.addAll(module.getInputs());
}
return result;
}
/**
* A module depth comparator that considers a deeper module to be
* "greater than" a shallower module. Uses module names to
* consistently break ties.
*/
private class DepthComparator implements Comparator<JSModule> {
public int compare(JSModule m1, JSModule m2) {
return depthCompare(m1, m2);
}
}
/**
* A module depth comparator that considers a deeper module to be "less than"
* a shallower module. Uses module names to consistently break ties.
*/
private class InverseDepthComparator implements Comparator<JSModule> {
public int compare(JSModule m1, JSModule m2) {
return depthCompare(m2, m1);
}
}
private int depthCompare(JSModule m1, JSModule m2) {
if (m1 == m2) {
return 0;
}
int d1 = m1.getDepth();
int d2 = m2.getDepth();
return d1 < d2 ? -1 : d2 == d1 ? m1.getName().compareTo(m2.getName()) : 1;
}
/*
* Exception class for declaring when the modules being fed into a
* JSModuleGraph as input aren't in dependence order, and so can't be
* processed for caching of various dependency-related queries.
*/
protected static class ModuleDependenceException
extends IllegalArgumentException {
private static final long serialVersionUID = 1;
private final JSModule module;
private
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> of all local
* variables and a variable is live if it is in the set.
*/
static class LiveVariableLattice implements LatticeElement {
private final BitSet liveSet;
/**
* @param numVars Number of all local variables.
*/
private LiveVariableLattice(int numVars) {
this.liveSet = new BitSet(numVars);
}
private LiveVariableLattice(LiveVariableLattice other) {
Preconditions.checkNotNull(other);
this.liveSet = (BitSet) other.liveSet.clone();
}
@Override
public boolean equals(Object other) {
Preconditions.checkNotNull(other);
return (other instanceof LiveVariableLattice) &&
this.liveSet.equals(((LiveVariableLattice) other).liveSet);
}
public boolean isLive(Var v) {
Preconditions.checkNotNull(v);
return liveSet.get(v.index);
}
public boolean isLive(int index) {
return liveSet.get(index);
}
@Override
public String toString() {
return liveSet.toString();
}
@Override
public int hashCode() {
return liveSet.hashCode();
}
}
// The scope of the function that we are analyzing.
private final Scope jsScope;
private final Set<Var> escaped;
LiveVariablesAnalysis(ControlFlowGraph<Node> cfg, Scope jsScope,
AbstractCompiler compiler) {
super(cfg, new LiveVariableJoinOp());
this.jsScope = jsScope;
this.escaped = Sets.newHashSet();
computeEscaped(jsScope, escaped, compiler);
}
public Set<Var> getEscapedLocals() {
return escaped;
}
public int getVarIndex(String var) {
return jsScope.getVar(var).index;
}
@Override
boolean isForward() {
return false;
}
@Override
LiveVariableLattice createEntryLattice() {
return new LiveVariableLattice(jsScope.getVarCount());
}
@Override
LiveVariableLattice createInitialEstimateLattice() {
return new LiveVariableLattice(jsScope.getVarCount());
}
@Override
LiveVariableLattice flowThrough(Node node, LiveVariableLattice input) {
final BitSet gen = new BitSet(input.liveSet.size());
final BitSet kill = new BitSet(input.liveSet.size());
// Make kills conditional if the node can end abruptly by an exception.
boolean conditional = false;
List<DiGraphEdge<Node, Branch>> edgeList = getCfg().getOutEdges(node);
for (DiGraphEdge<Node, Branch> edge : edgeList) {
if (Branch.ON_EX.equals(edge.getValue())) {
conditional = true;
}
}
computeGenKill(node, gen, kill, conditional);
LiveVariableLattice result = new LiveVariableLattice(input);
// L_in = L_out - Kill + Gen
result.liveSet.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>andNot(kill);
result.liveSet.or(gen);
return result;
}
/**
* Computes the GEN and KILL set.
*
* @param n Root node.
* @param gen Local variables that are live because of the instruction at
* {@code n} will be added to this set.
* @param kill Local variables that are killed because of the instruction at
* {@code n} will be added to this set.
* @param conditional {@code true} if any assignments encountered are
* conditionally executed. These assignments might not kill a variable.
*/
private void computeGenKill(Node n, BitSet gen, BitSet kill,
boolean conditional) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.WHILE:
case Token.DO:
case Token.IF:
computeGenKill(NodeUtil.getConditionExpression(n), gen, kill,
conditional);
return;
case Token.FOR:
if (!NodeUtil.isForIn(n)) {
computeGenKill(NodeUtil.getConditionExpression(n), gen, kill,
conditional);
} else {
// for(x in y) {...}
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (NodeUtil.isVar(lhs)) {
// for(var x in y) {...}
lhs = lhs.getLastChild();
}
addToSetIfLocal(lhs, kill);
addToSetIfLocal(lhs, gen);
computeGenKill(rhs, gen, kill, conditional);
}
return;
case Token.VAR:
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.hasChildren()) {
computeGenKill(c.getFirstChild(), gen, kill, conditional);
if (!conditional) {
addToSetIfLocal(c, kill);
}
}
}
return;
case Token.AND:
case Token.OR:
computeGenKill(n.getFirstChild(), gen, kill, conditional);
// May short circuit.
computeGenKill(n.getLastChild(), gen, kill, true);
return;
case Token.HOOK:
computeGenKill(n.getFirstChild(), gen, kill, conditional);
// Assume both sides are conditional.
computeGenKill(n.getFirstChild().getNext(), gen, kill, true);
computeGenKill(n.getLastChild(), gen, kill, true);
return;
case Token.NAME:
if (isArgumentsName(n)) {
markAllParametersEscaped();
} else {
addToSetIfLocal(n, gen);
}
return;
default:
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isName(n.getFirstChild())) {
Node lhs = n.getFirstChild();
if (!conditional) {
addToSetIfLocal(lhs, kill);
}
if (!NodeUtil
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.isAssign(n)) {
// assignments such as a += 1 reads a.
addToSetIfLocal(lhs, gen);
}
computeGenKill(lhs.getNext(), gen, kill, conditional);
} else {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
computeGenKill(c, gen, kill, conditional);
}
}
return;
}
}
private void addToSetIfLocal(Node node, BitSet set) {
Preconditions.checkState(NodeUtil.isName(node));
String name = node.getString();
if (!jsScope.isDeclared(name, false)) {
return;
}
Var var = jsScope.getVar(name);
if (!escaped.contains(var)) {
set.set(var.index);
}
}
/**
* Give up computing liveness of formal parameter by putting all the parameter
* names in the escaped set.
*/
void markAllParametersEscaped() {
Node lp = jsScope.getRootNode().getFirstChild().getNext();
for(Node arg = lp.getFirstChild(); arg != null; arg = arg.getNext()) {
escaped.add(jsScope.getVar(arg.getString()));
}
}
private boolean isArgumentsName(Node n) {
if (n.getType() != Token.NAME ||
!n.getString().equals(ARGUMENT_ARRAY_ALIAS) ||
jsScope.isDeclared(ARGUMENT_ARRAY_ALIAS, false)) {
return false;
} else {
return true;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
import javax.annotation.Nullable;
/**
* Peephole optimization to remove useless code such as IF's with false
* guard conditions, comma operator left hand sides with no side effects, etc.
*
*/
public class PeepholeRemoveDeadCode extends AbstractPeepholeOptimization {
// TODO(dcc): Some (all) of these can probably be better achieved
// using the control flow graph (like CheckUnreachableCode).
// There is an existing CFG pass (UnreachableCodeElimination) that
// could be changed to use code from CheckUnreachableCode to do this.
@Override
Node optimizeSubtree(Node subtree) {
switch(subtree.getType()) {
case Token.COMMA:
return tryFoldComma(subtree);
case Token.SCRIPT:
case Token.BLOCK:
return tryOptimizeBlock(subtree);
case Token.EXPR_RESULT:
subtree = tryFoldExpr(subtree);
return subtree;
case Token.HOOK:
return tryFoldHook(subtree);
case Token.SWITCH:
return tryOptimizeSwitch(subtree);
case Token.IF:
return tryFoldIf(subtree);
case Token.WHILE:
return tryFoldWhile(subtree);
case Token.FOR: {
Node condition = NodeUtil.getConditionExpression(subtree);
if (condition != null) {
tryFoldForCondition(condition);
}
}
return tryFoldFor(subtree);
case Token.DO:
return tryFoldDo(subtree);
default:
return subtree;
}
}
/**
* Try folding EXPR_RESULT nodes by removing useless Ops and expressions.
* @return the replacement node, if changed, or the original if not
*/
private Node tryFoldExpr(Node subtree) {
Node result = trySimpilifyUnusedResult(subtree.getFirstChild());
if (result == null) {
Node parent = subtree.getParent();
// If the EXPR_RESULT no longer has any children
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>, remove it as well.
if (parent.getType() == Token.LABEL) {
Node replacement = new Node(Token.BLOCK).copyInformationFrom(subtree);
parent.replaceChild(subtree, replacement);
subtree = replacement;
} else {
subtree.detachFromParent();
subtree = null;
}
}
return subtree;
}
/**
* General cascading unused operation node removal.
* @param n The root of the expression to simplify.
* @return The replacement node, or null if the node was is not useful.
*/
private Node trySimpilifyUnusedResult(Node n) {
return trySimpilifyUnusedResult(n, true);
}
/**
* General cascading unused operation node removal.
* @param n The root of the expression to simplify.
* @param removeUnused If true, the node is removed from the AST if
* it is not useful, otherwise it replaced with an EMPTY node.
* @return The replacement node, or null if the node was is not useful.
*/
private Node trySimpilifyUnusedResult(Node n, boolean removeUnused) {
Node result = n;
// Simplify the results of conditional expressions
switch (n.getType()) {
case Token.HOOK:
Node trueNode = trySimpilifyUnusedResult(n.getFirstChild().getNext());
Node falseNode = trySimpilifyUnusedResult(n.getLastChild());
// If one or more of the conditional children were removed,
// transform the HOOK to an equivalent operation:
// x() ? foo() : 1 --> x() && foo()
// x() ? 1 : foo() --> x() || foo()
// x() ? 1 : 1 --> x()
// x ? 1 : 1 --> null
if (trueNode == null && falseNode != null) {
n.setType(Token.OR);
Preconditions.checkState(n.getChildCount() == 2);
} else if (trueNode != null && falseNode == null) {
n.setType(Token.AND);
Preconditions.checkState(n.getChildCount() == 2);
} else if (trueNode == null && falseNode == null) {
result = trySimpilifyUnusedResult(n.getFirstChild());
} else {
// The structure didn't change.
result = n;
}
break;
case Token.AND:
case Token.OR:
// Try to remove the second operand from a AND or OR operations:
// x() || f --> x()
// x() && f --> x()
Node conditionalResultNode = trySimpilifyUnusedResult(
n.getLastChild());
if (conditionalResultNode == null) {
Preconditions.checkState(n.hasOneChild());
// The conditionally executed code was removed, so
// replace the AND/OR with its LHS or remove it if it isn't useful.
result = trySimpilifyUnusedResult(n.getFirstChild());
}
break;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
case Token.FUNCTION:
// A function expression isn't useful if it isn't used, remove it and
// don't bother to look at its children.
result = null;
break;
case Token.COMMA:
// We rewrite other operations as COMMA expressions (which will later
// get split into individual EXPR_RESULT statement, if possible), so
// we special case COMMA (we don't want to rewrite COMMAs as new COMMAs
// nodes.
Node left = trySimpilifyUnusedResult(n.getFirstChild());
Node right = trySimpilifyUnusedResult(n.getLastChild());
if (left == null && right == null) {
result = null;
} else if (left == null) {
result = right;
} else if (right == null){
result = left;
} else {
// The structure didn't change.
result = n;
}
break;
default:
if (!NodeUtil.nodeTypeMayHaveSideEffects(n)) {
// This is the meat of this function. The node itself doesn't generate
// any side-effects but preserve any side-effects in the children.
Node resultList = null;
for (Node next, c = n.getFirstChild(); c != null; c = next) {
next = c.getNext();
c = trySimpilifyUnusedResult(c);
if (c != null) {
c.detachFromParent();
if (resultList == null) {
// The first side-effect can be used stand-alone.
resultList = c;
} else {
// Leave the side-effects in-place, simplifying it to a COMMA
// expression.
resultList = new Node(Token.COMMA, resultList, c)
.copyInformationFrom(c);
}
}
}
result = resultList;
}
}
// Fix up the AST, replace or remove the an unused node (if requested).
if (n != result) {
Node parent = n.getParent();
if (result == null) {
if (removeUnused) {
parent.removeChild(n);
} else {
result = new Node(Token.EMPTY).copyInformationFrom(n);
parent.replaceChild(n, result);
}
} else {
// A new COMMA expression may not have an existing parent.
if (result.getParent() != null) {
result.detachFromParent();
}
n.getParent().replaceChild(n, result);
}
reportCodeChange();
}
return result;
}
/**
* Remove useless switches and cases.
*/
private Node tryOptimizeSwitch(Node n) {
Preconditions.checkState(n.getType() == Token.SWITCH);
Node defaultCase = tryOptimizeDefaultCase(n);
// Removing cases when there exists a default case is not safe.
if (defaultCase == null) {
Node next = null;
Node prev = null;
// The first child is the switch conditions skip it.
for (Node c
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> = n.getFirstChild().getNext(); c != null; c = next) {
next = c.getNext();
if (!mayHaveSideEffects(c.getFirstChild()) && isUselessCase(c, prev)) {
removeCase(n, c);
} else {
prev = c;
}
}
}
// Remove the switch if there are no remaining cases.
if (n.hasOneChild()) {
Node condition = n.removeFirstChild();
Node parent = n.getParent();
Node replacement = new Node(Token.EXPR_RESULT, condition)
.copyInformationFrom(n);
parent.replaceChild(n, replacement);
reportCodeChange();
return replacement;
}
return null;
}
/**
* @return the default case node or null if there is no default case or
* if the default case is removed.
*/
private Node tryOptimizeDefaultCase(Node n) {
Preconditions.checkState(n.getType() == Token.SWITCH);
Node lastNonRemovable = n.getFirstChild(); // The switch condition
// The first child is the switch conditions skip it when looking for cases.
for (Node c = n.getFirstChild().getNext(); c != null; c = c.getNext()) {
if (c.getType() == Token.DEFAULT) {
// Remove cases that fall-through to the default case
Node caseToRemove = lastNonRemovable.getNext();
for (Node next; caseToRemove != c; caseToRemove = next) {
next = caseToRemove.getNext();
removeCase(n, caseToRemove);
}
// Don't use the switch condition as the previous case.
Node prevCase = (lastNonRemovable == n.getFirstChild())
? null : lastNonRemovable;
// Remove the default case if we can
if (isUselessCase(c, prevCase)) {
removeCase(n, c);
return null;
}
return c;
} else {
Preconditions.checkState(c.getType() == Token.CASE);
if (c.getLastChild().hasChildren()
|| mayHaveSideEffects(c.getFirstChild())) {
lastNonRemovable = c;
}
}
}
return null;
}
/**
* Remove the case from the switch redeclaring any variables declared in it.
* @param caseNode The case to remove.
*/
private void removeCase(Node switchNode, Node caseNode) {
NodeUtil.redeclareVarsInsideBranch(caseNode);
switchNode.removeChild(caseNode);
reportCodeChange();
}
/**
* The function assumes that when checking a CASE node there is no
* DEFAULT node in the SWITCH.
* @return Whether the CASE or DEFAULT block does anything useful.
*/
private boolean isUselessCase(Node caseNode, @Nullable Node previousCase) {
Preconditions.checkState(
previousCase == null || previousCase.getNext() == caseNode);
// A case isn't useless can't be useless if a previous case falls
// through to it unless
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> it happens to be the last case in the switch.
Node switchNode = caseNode.getParent();
if (switchNode.getLastChild() != caseNode
&& previousCase != null) {
Node previousBlock = previousCase.getLastChild();
if (!previousBlock.hasChildren()
|| !isExit(previousBlock.getLastChild())) {
return false;
}
}
Node executingCase = caseNode;
while (executingCase != null) {
Preconditions.checkState(executingCase.getType() == Token.DEFAULT
|| executingCase.getType() == Token.CASE);
// We only expect a DEFAULT case if the case we are checking is the
// DEFAULT case. Otherwise we assume the DEFAULT case has already
// been removed.
Preconditions.checkState(caseNode == executingCase
|| executingCase.getType() != Token.DEFAULT);
Node block = executingCase.getLastChild();
Preconditions.checkState(block.getType() == Token.BLOCK);
if (block.hasChildren()) {
for (Node blockChild : block.children()) {
int type = blockChild.getType();
// If this is a block with a labelless break, it is useless.
switch (blockChild.getType()) {
case Token.BREAK:
// A break to a different control structure isn't useless.
return blockChild.getFirstChild() == null;
case Token.VAR:
if (blockChild.hasOneChild()
&& blockChild.getFirstChild().getFirstChild() == null) {
// Variable declarations without initializations are ok.
continue;
}
return false;
default:
return false;
}
}
} else {
// Look at the fallthrough case
executingCase = executingCase.getNext();
}
}
return true;
}
/**
* @return Whether the node is an obvious control flow exit.
*/
private boolean isExit(Node n) {
switch (n.getType()) {
case Token.BREAK:
case Token.CONTINUE:
case Token.RETURN:
case Token.THROW:
return true;
default:
return false;
}
}
private Node tryFoldComma(Node n) {
// If the left side does nothing replace the comma with the result.
Node parent = n.getParent();
Node left = n.getFirstChild();
Node right = left.getNext();
left = trySimpilifyUnusedResult(left);
if (left == null || !mayHaveSideEffects(left)) {
// Fold it!
n.removeChild(right);
parent.replaceChild(n, right);
reportCodeChange();
return right;
} else {
if (parent.getType() == Token.EXPR_RESULT
&& parent.getParent().getType() != Token.LABEL) {
// split comma
n.detachChildren();
// Replace the original expression with the left operand.
parent.replaceChild(n, left);
// Add the right expression afterward.
Node newStatement = new Node(Token.EXPR_RESULT, right);
newStatement.copyInformationFrom(n
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>value != TernaryValue.UNKNOWN) {
int replacementConditionNodeType =
(value.toBoolean(true)) ? Token.TRUE : Token.FALSE;
condition.getParent().replaceChild(condition,
new Node(replacementConditionNodeType));
reportCodeChange();
}
}
}
}
/**
* @return whether the node is a assignment to a simple name, or simple var
* declaration with initialization.
*/
private boolean isSimpleAssignment(Node n) {
// For our purposes we define a simple assignment to be a assignment
// to a NAME node, or a VAR declaration with one child and a initializer.
if (NodeUtil.isExprAssign(n)
&& NodeUtil.isName(n.getFirstChild().getFirstChild())) {
return true;
} else if (n.getType() == Token.VAR && n.hasOneChild() &&
n.getFirstChild().getFirstChild() != null) {
return true;
}
return false;
}
/**
* @return The name being assigned to.
*/
private Node getSimpleAssignmentName(Node n) {
Preconditions.checkState(isSimpleAssignment(n));
if (NodeUtil.isExprAssign(n)) {
return n.getFirstChild().getFirstChild();
} else {
// A var declaration.
return n.getFirstChild();
}
}
/**
* @return The value assigned in the simple assignment
*/
private Node getSimpleAssignmentValue(Node n) {
Preconditions.checkState(isSimpleAssignment(n));
return n.getFirstChild().getLastChild();
}
/**
* @return Whether the node is a conditional statement.
*/
private boolean isConditionalStatement(Node n) {
// We defined a conditional statement to be a IF or EXPR_RESULT rooted with
// a HOOK, AND, or OR node.
return n != null && (n.getType() == Token.IF || isExprConditional(n));
}
/**
* @return Whether the node is a rooted with a HOOK, AND, or OR node.
*/
private boolean isExprConditional(Node n) {
if (n.getType() == Token.EXPR_RESULT) {
switch (n.getFirstChild().getType()) {
case Token.HOOK:
case Token.AND:
case Token.OR:
return true;
}
}
return false;
}
/**
* @return The condition of a conditional statement.
*/
private Node getConditionalStatementCondition(Node n) {
if (n.getType() == Token.IF) {
return NodeUtil.getConditionExpression(n);
} else {
Preconditions.checkState(isExprConditional(n));
return n.getFirstChild().getFirstChild();
}
}
/**
* Try folding IF nodes by removing dead branches.
* @return the replacement node, if changed, or the original if not
*/
private Node tryFoldIf(Node n) {
Preconditions.checkState(n.getType() == Token.IF);
Node parent = n.getParent
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>();
Preconditions.checkNotNull(parent);
int type = n.getType();
Node cond = n.getFirstChild();
Node thenBody = cond.getNext();
Node elseBody = thenBody.getNext();
// if (x) { .. } else { } --> if (x) { ... }
if (elseBody != null && !mayHaveSideEffects(elseBody)) {
n.removeChild(elseBody);
elseBody = null;
reportCodeChange();
}
// if (x) { } else { ... } --> if (!x) { ... }
if (!mayHaveSideEffects(thenBody) && elseBody != null) {
n.removeChild(elseBody);
n.replaceChild(thenBody, elseBody);
Node notCond = new Node(Token.NOT);
n.replaceChild(cond, notCond);
notCond.addChildToFront(cond);
cond = notCond;
thenBody = cond.getNext();
elseBody = null;
reportCodeChange();
}
// if (x()) { }
if (!mayHaveSideEffects(thenBody) && elseBody == null) {
if (mayHaveSideEffects(cond)) {
// x() has side effects, just leave the condition on its own.
n.removeChild(cond);
Node replacement = NodeUtil.newExpr(cond);
parent.replaceChild(n, replacement);
reportCodeChange();
return replacement;
} else {
// x() has no side effects, the whole tree is useless now.
NodeUtil.removeChild(parent, n);
reportCodeChange();
return null;
}
}
// Try transforms that apply to both IF and HOOK.
TernaryValue condValue = NodeUtil.getExpressionBooleanValue(cond);
if (condValue == TernaryValue.UNKNOWN) {
return n; // We can't remove branches otherwise!
}
if (mayHaveSideEffects(cond)) {
// Transform "if (a = 2) {x =2}" into "if (true) {a=2;x=2}"
boolean newConditionValue = condValue == TernaryValue.TRUE;
// Add an elseBody if it is needed.
if (!newConditionValue && elseBody == null) {
elseBody = new Node(Token.BLOCK).copyInformationFrom(n);
n.addChildToBack(elseBody);
}
Node newCond = new Node(newConditionValue ? Token.TRUE : Token.FALSE);
n.replaceChild(cond, newCond);
Node branchToKeep = newConditionValue ? thenBody : elseBody;
branchToKeep.addChildToFront(
new Node(Token.EXPR_RESULT, cond).copyInformationFrom(cond));
reportCodeChange();
cond = newCond;
}
boolean condTrue = condValue.toBoolean(true);
if (n.getChildCount() == 2) {
Preconditions.checkState(type == Token.IF);
if (condTrue) {
// Replace "if (true) { X }" with "X
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>".
Node thenStmt = n.getFirstChild().getNext();
n.removeChild(thenStmt);
parent.replaceChild(n, thenStmt);
reportCodeChange();
return thenStmt;
} else {
// Remove "if (false) { X }" completely.
NodeUtil.redeclareVarsInsideBranch(n);
NodeUtil.removeChild(parent, n);
reportCodeChange();
return null;
}
} else {
// Replace "if (true) { X } else { Y }" with X, or
// replace "if (false) { X } else { Y }" with Y.
Node trueBranch = n.getFirstChild().getNext();
Node falseBranch = trueBranch.getNext();
Node branchToKeep = condTrue ? trueBranch : falseBranch;
Node branchToRemove = condTrue ? falseBranch : trueBranch;
NodeUtil.redeclareVarsInsideBranch(branchToRemove);
n.removeChild(branchToKeep);
parent.replaceChild(n, branchToKeep);
reportCodeChange();
return branchToKeep;
}
}
/**
* Try folding HOOK (?:) if the condition results of the condition is known.
* @return the replacement node, if changed, or the original if not
*/
private Node tryFoldHook(Node n) {
Preconditions.checkState(n.getType() == Token.HOOK);
Node parent = n.getParent();
Preconditions.checkNotNull(parent);
Node cond = n.getFirstChild();
Node thenBody = cond.getNext();
Node elseBody = thenBody.getNext();
TernaryValue condValue = NodeUtil.getExpressionBooleanValue(cond);
if (condValue == TernaryValue.UNKNOWN) {
return n; // We can't remove branches otherwise!
}
// Transform "(a = 2) ? x =2 : y" into "a=2,x=2"
n.detachChildren();
Node branchToKeep = condValue.toBoolean(true) ? thenBody : elseBody;
Node replacement;
if (mayHaveSideEffects(cond)) {
replacement = new Node(Token.COMMA).copyInformationFrom(n);
replacement.addChildToFront(cond);
replacement.addChildToBack(branchToKeep);
} else {
replacement = branchToKeep;
}
parent.replaceChild(n, replacement);
reportCodeChange();
return replacement;
}
/**
* Removes WHILEs that always evaluate to false.
*/
Node tryFoldWhile(Node n) {
Preconditions.checkArgument(n.getType() == Token.WHILE);
Node cond = NodeUtil.getConditionExpression(n);
if (NodeUtil.getBooleanValue(cond) != TernaryValue.FALSE) {
return n;
}
NodeUtil.redeclareVarsInsideBranch(n);
NodeUtil.removeChild(n.getParent(), n);
reportCodeChange();
return null;
}
/**
* Removes FORs that always evaluate to false.
*/
Node tryFoldFor(Node n) {
Preconditions.check
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Argument(n.getType() == Token.FOR);
// If this is a FOR-IN loop skip it.
if (NodeUtil.isForIn(n)) {
return n;
}
Node init = n.getFirstChild();
Node cond = init.getNext();
Node increment = cond.getNext();
if (init.getType() != Token.EMPTY && init.getType() != Token.VAR) {
init = trySimpilifyUnusedResult(init, false);
}
if (increment.getType() != Token.EMPTY) {
increment = trySimpilifyUnusedResult(increment, false);
}
// There is an initializer skip it
if (n.getFirstChild().getType() != Token.EMPTY) {
return n;
}
if (NodeUtil.getBooleanValue(cond) != TernaryValue.FALSE) {
return n;
}
NodeUtil.redeclareVarsInsideBranch(n);
NodeUtil.removeChild(n.getParent(), n);
reportCodeChange();
return null;
}
/**
* Removes DOs that always evaluate to false. This leaves the
* statements that were in the loop in a BLOCK node.
* The block will be removed in a later pass, if possible.
*/
Node tryFoldDo(Node n) {
Preconditions.checkArgument(n.getType() == Token.DO);
Node cond = NodeUtil.getConditionExpression(n);
if (NodeUtil.getBooleanValue(cond) != TernaryValue.FALSE) {
return n;
}
// TODO(johnlenz): The do-while can be turned into a label with
// named breaks and the label optimized away (maybe).
if (hasBreakOrContinue(n)) {
return n;
}
Preconditions.checkState(
NodeUtil.isControlStructureCodeBlock(n, n.getFirstChild()));
Node block = n.removeFirstChild();
n.getParent().replaceChild(n, block);
reportCodeChange();
return n;
}
/**
*
*/
boolean hasBreakOrContinue(Node n) {
// TODO(johnlenz): This is overkill as named breaks may refer to outer
// loops or labels, and any break my refer to an inner loop.
// More generally, this check may be more expensive than we like.
return NodeUtil.has(
n,
Predicates.<Node>or(
new NodeUtil.MatchNodeType(Token.BREAK),
new NodeUtil.MatchNodeType(Token.CONTINUE)),
new NodeUtil.MatchNotFunction());
}
/**
* Remove always true loop conditions.
*/
private void tryFoldForCondition(Node forCondition) {
if (NodeUtil.getBooleanValue(forCondition) == TernaryValue.TRUE) {
forCondition.getParent().replaceChild(forCondition,
new Node(Token.EMPTY));
reportCodeChange();
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> private Node synthesizedExternsRoot = null;
// Vars that still need to be declared in externs. These will be declared
// at the end of the pass, or when we see the equivalent var declared
// in the normal code.
private Set<String> varsToDeclareInExterns = Sets.newHashSet();
private final AbstractCompiler compiler;
// Whether this is the post-processing sanity check.
private final boolean sanityCheck;
// Whether extern checks emit error.
private boolean strictExternCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.strictExternCheck = compiler.getErrorLevel(
JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
// Don't run externs-checking in sanity check mode. Normalization will
// remove duplicate VAR declarations, which will make
// externs look like they have assigns.
if (!sanityCheck) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
}
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
for (String varName : varsToDeclareInExterns) {
createSynthesizedExternVar(varName);
}
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.NAME) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(NodeUtil.isFunction(parent));
// A function declaration with an empty name passes Rhino,
// but is supposed to be a syntax error according to the spec.
if (!NodeUtil.isFunctionExpression(parent)) {
t.report(n, INVALID_FUNCTION_DECL);
}
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.getType() == Token.VAR ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
parent.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(varName, n,
null, getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This is fine.
return;
}
// Check module dependencies.
JSModule currModule = currInput.getModule();
JSModule varModule = varInput.getModule();
JSModuleGraph moduleGraph = compiler.getModuleGraph();
if (varModule != currModule && varModule != null && currModule != null) {
if (moduleGraph.dependsOn(currModule, varModule)) {
// The module dependency was properly declared.
} else {
if (!sanityCheck && scope.isGlobal()) {
if (moduleGraph.dependsOn(varModule, currModule)) {
// The variable reference violates a declared module dependency.
t.report(n, VIOLATED_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
} else {
// The variable reference is between two modules that have no
// dependency relationship. This should probably be considered an
// error, but just issue a warning for now.
t.report(n, MISSING_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
} else {
t.report(n, STRICT_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
}
}
}
/**
* Create a new variable in a synthetic script. This will prevent
* subsequent compiler passes from crashing.
*/
private void createSynthesizedExternVar(String varName) {
Node nameNode = Node.newString(Token.NAME, varName);
// Mark the variable as constant if it matches the coding convention
// for constant vars.
// NOTE(nicksantos): honestly, i'm not sure how much this matters.
// AFAIK, all people who use the CONST coding convention also
// compile with undeclaredVars as errors. We have some test
// cases for this configuration though, and it makes them happier.
if (compiler.getCodingConvention().isConstant(varName)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
getSynthesizedExternsRoot().addChildToBack(
new Node(Token.VAR, nameNode));
varsToDeclareInExterns.remove(varName);
}
/**
* A check for name references in the externs inputs. These used to prevent
* a
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> variable from getting renamed, but no longer have any effect.
*/
private class NameRefInExternsCheck extends AbstractPostOrderCallback {
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.LP:
// These are okay.
break;
case Token.GETPROP:
if (n == parent.getFirstChild()) {
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString());
varsToDeclareInExterns.add(n.getString());
}
}
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
varsToDeclareInExterns.add(n.getString());
}
break;
}
}
}
}
/** Lazily create a "new" externs input for undeclared variables. */
private CompilerInput getSynthesizedExternsInput() {
if (synthesizedExternsInput == null) {
synthesizedExternsInput =
compiler.newExternInput("{SyntheticVarsDeclar}");
}
return synthesizedExternsInput;
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
if (synthesizedExternsRoot == null) {
CompilerInput synthesizedExterns = getSynthesizedExternsInput();
synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler);
}
return synthesizedExternsRoot;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(file));
}
/** Adds a source file input to this module. */
public void addFirst(JSSourceFile file) {
addFirst(new CompilerInput(file));
}
/** Adds a source code input to this module. */
public void add(CompilerInput input) {
inputs.add(input);
input.setModule(this);
}
/** Adds a source code input to this module. */
public void addFirst(CompilerInput input) {
inputs.add(0, input);
input.setModule(this);
}
/** Adds a source code input to this module directly after other. */
public void addAfter(CompilerInput input, CompilerInput other) {
Preconditions.checkState(inputs.contains(other));
inputs.add(inputs.indexOf(other), input);
input.setModule(this);
}
/** Adds a dependency on another module. */
public void addDependency(JSModule dep) {
Preconditions.checkState(dep != this);
deps.add(dep);
}
/** Removes an input from this module. */
public void remove(CompilerInput input) {
input.setModule(null);
inputs.remove(input);
}
/** Removes all of the inputs from this module. */
public void removeAll() {
for (CompilerInput input : inputs) {
input.setModule(null);
}
inputs.clear();
}
/**
* Gets the list of modules that this module depends on.
*
* @return A list that may be empty but not null
*/
public List<JSModule> getDependencies() {
return deps;
}
/**
* Gets the names of the modules that this module depends on,
* sorted alphabetically.
*/
List<String> getSortedDependencyNames() {
List<String> names = Lists.newArrayList();
for (JSModule module : getDependencies()) {
names.add(module.getName());
}
Collections.sort(names);
return names;
}
/**
* Returns the transitive closure of dependencies starting from the
* dependencies of this module.
*/
public Set<JSModule> getAllDependencies() {
Set<JSModule> allDeps = Sets.newHashSet(deps);
List<JSModule> workList = Lists.newArrayList(deps);
while (workList.size() > 0) {
JSModule module = workList.remove(workList.size() - 1);
for (JSModule dep : module.getDependencies()) {
if (allDeps.add(dep)) {
workList.add(dep);
}
}
}
return allDeps;
}
/** Returns this module and all of its dependencies in one list. */
public Set<JSModule> getThisAndAllDependencies() {
Set<JSModule> deps = getAllDependencies();
deps.add(this);
return deps;
}
/**
* Gets this module's list of source code inputs.
*
* @return A list that may be empty but not null
*/
public
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
this.oldName = name;
this.newName = null;
this.count = 0;
// Represents the order at which a symbol appears in the source.
this.orderOfOccurrence = assignmentCount++;
}
/**
* Assigns the new name.
*/
void setNewName(String newName) {
Preconditions.checkState(this.newName == null);
this.newName = newName;
}
}
/** Maps an old name to a new name assignment */
private final SortedMap<String, Assignment> assignments =
new TreeMap<String, Assignment>();
/** Whether renaming should apply to local variables only. */
private final boolean localRenamingOnly;
/**
* Whether function expression names should be preserved. Typically, for
* debugging purposes.
* @see NameAnonymousFunctions
*/
private boolean preserveFunctionExpressionNames;
/** Characters that shouldn't be used in variable names. */
private final char[] reservedCharacters;
/** A prefix to distinguish temporary local names from global names */
private static final String LOCAL_VAR_PREFIX = "L ";
RenameVars(AbstractCompiler compiler,
String prefix,
boolean localRenamingOnly,
boolean preserveFunctionExpressionNames,
boolean generatePseudoNames,
VariableMap prevUsedRenameMap,
@Nullable char[] reservedCharacters,
@Nullable Set<String> reservedNames) {
this.compiler = compiler;
this.prefix = prefix == null ? "" : prefix;
this.localRenamingOnly = localRenamingOnly;
this.preserveFunctionExpressionNames = preserveFunctionExpressionNames;
if (generatePseudoNames) {
this.pseudoNameMap = Maps.newHashMap();
} else {
this.pseudoNameMap = null;
}
this.prevUsedRenameMap = prevUsedRenameMap;
this.reservedCharacters = reservedCharacters;
if (reservedNames == null) {
this.reservedNames = Sets.newHashSet();
} else {
this.reservedNames = Sets.newHashSet(reservedNames);
}
}
/**
* Iterate through the nodes, collect all the NAME nodes that need to be
* renamed, and count how many times each variable name is referenced.
*
* There are 2 passes:
* - externs: keep track of the global vars in the externNames_ map.
* - source: keep track of all name references in globalNameNodes_, and
* localNameNodes_.
*
* To get shorter local variable renaming, we rename local variables to a
* temporary name "LOCAL_VAR_PREFIX + index" where index is the index of the
* variable declared in the local scope stack.
* e.g.
* Foo(fa, fb) {
* var c = function(d, e) { return fa; }
* }
* The indexes are: fa:0, fb:1, c:2, d:3, e:4
*
* In that way, local variable names are reused in each global function.
* e.g. the final code might look like
* function x(a
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>,b) { ... }
* function y(a,b,c) { ... }
*/
class ProcessVars extends AbstractPostOrderCallback {
private final boolean isExternsPass_;
ProcessVars(boolean isExterns) {
isExternsPass_ = isExterns;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.NAME) {
return;
}
String name = n.getString();
// Ignore anonymous functions
if (name.length() == 0) {
return;
}
// Is this local or Global?
Scope.Var var = t.getScope().getVar(name);
boolean local = (var != null) && var.isLocal();
// Are we renaming global variables?
if (!local && localRenamingOnly) {
reservedNames.add(name);
return;
}
// Are we renaming function expression names?
if (preserveFunctionExpressionNames
&& var != null
&& NodeUtil.isFunctionExpression(var.getParentNode())) {
reservedNames.add(name);
return;
}
// Check if we can rename this.
if (!okToRenameVar(name, local)) {
if (local) {
// Blindly de-uniquify for the Prototype library for issue 103.
String newName =
MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName(
name);
if (!newName.equals(name)) {
n.setString(newName);
}
}
return;
}
if (isExternsPass_) {
// Keep track of extern globals.
if (!local) {
externNames.add(name);
}
return;
}
if (pseudoNameMap != null) {
recordPseudoName(n);
}
if (local) {
// Local var: assign a new name
String tempName = LOCAL_VAR_PREFIX + var.getLocalVarIndex();
incCount(tempName, null);
localNameNodes.add(n);
n.setString(tempName);
} else if (var != null) { // Not an extern
// If it's global, increment global count
incCount(name, var.input);
globalNameNodes.add(n);
}
}
// Increment count of an assignment
void incCount(String name, CompilerInput input) {
Assignment s = assignments.get(name);
if (s == null) {
s = new Assignment(name, input);
assignments.put(name, s);
}
s.count++;
}
}
/**
* Sorts Assignment objects by their count, breaking ties by their
* order of occurrence in the source to ensure a deterministic total
* ordering.
*/
private static final Comparator<Assignment> FREQUENCY_COMPARATOR =
new Comparator<Assignment>() {
public int compare(Assignment a1, Assignment a2) {
if (a1.count != a
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> *
* The test for Var equality uses reference equality, so it's necessary to
* inject a scope when you traverse.
*/
ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior,
Predicate<Var> varFilter) {
this.compiler = compiler;
this.behavior = behavior;
this.varFilter = varFilter;
}
/**
* Convenience method for running this pass over a tree with this
* class as a callback.
*/
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
/**
* Gets the variables that were referenced in this callback.
*/
public Set<Var> getReferencedVariables() {
return referenceMap.keySet();
}
/**
* Gets the reference collection for the given variable.
*/
public ReferenceCollection getReferenceCollection(Var v) {
return referenceMap.get(v);
}
/**
* For each node, update the block stack and reference collection
* as appropriate.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
Var v = t.getScope().getVar(n.getString());
if (v != null && varFilter.apply(v)) {
addReference(t, v,
new Reference(n, parent, t, blockStack.peek()));
}
}
if (isBlockBoundary(n, parent)) {
blockStack.pop();
}
}
/**
* Updates block stack and invokes any additional behavior.
*/
public void enterScope(NodeTraversal t) {
Node n = t.getScope().getRootNode();
BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek();
blockStack.push(new BasicBlock(parent, n));
}
/**
* Updates block statck and invokes any additional behavior.
*/
public void exitScope(NodeTraversal t) {
blockStack.pop();
behavior.afterExitScope(t, referenceMap);
}
/**
* Updates block stack.
*/
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// If node is a new basic block, put on basic block stack
if (isBlockBoundary(n, parent)) {
blockStack.push(new BasicBlock(blockStack.peek(), n));
}
return true;
}
/**
* @return true if this node marks the start of a new basic block
*/
private static boolean isBlockBoundary(Node n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explcit CATCH token but no explicit
// FINALLY token. For simplicity
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>, we consider each BLOCK
// a separate basic BLOCK.
return true;
case Token.AND:
case Token.HOOK:
case Token.IF:
case Token.OR:
// The first child of a conditional is not a boundary,
// but all the rest of the children are.
return n != parent.getFirstChild();
}
}
return n.getType() == Token.CASE;
}
private void addReference(NodeTraversal t, Var v, Reference reference) {
// Create collection if none already
ReferenceCollection referenceInfo = referenceMap.get(v);
if (referenceInfo == null) {
referenceInfo = new ReferenceCollection();
referenceMap.put(v, referenceInfo);
}
// Add this particular reference
referenceInfo.add(reference, t, v);
}
/**
* Way for callers to add specific behavior during traversal that
* utilizes the built-up reference information.
*/
interface Behavior {
/**
* Called after we finish with a scope.
*/
void afterExitScope(NodeTraversal t,
Map<Var, ReferenceCollection> referenceMap);
}
static Behavior DO_NOTHING_BEHAVIOR = new Behavior() {
@Override
public void afterExitScope(NodeTraversal t,
Map<Var, ReferenceCollection> referenceMap) {}
};
/**
* A collection of references. Can be subclassed to apply checks or
* store additional state when adding.
*/
static class ReferenceCollection {
List<Reference> references = Lists.newArrayList();
void add(Reference reference, NodeTraversal t, Var v) {
references.add(reference);
}
/**
* Determines if the variable for this reference collection is
* "well-defined." A variable is well-defined if we can prove at
* compile-time that it's assigned a value before it's used.
*
* Notice that if this function returns false, this doesn't imply that the
* variable is used before it's assigned. It just means that we don't
* have enough information to make a definitive judgement.
*/
protected boolean isWellDefined() {
int size = references.size();
if (size == 0) {
return false;
}
// If this is a declaration that does not instantiate the variable,
// it's not well-defined.
Reference init = getInitializingReference();
if (init == null) {
return false;
}
Preconditions.checkState(references.get(0).isDeclaration());
BasicBlock initBlock = init.getBasicBlock();
for (int i = 1; i < size; i++) {
if (!initBlock.provablyExecutesBefore(
references.get(i).getBasicBlock())) {
return false;
}
}
return true;
}
/**
* Whether the variable is escaped into an inner scope.
*/
boolean isEscaped() {
Scope scope = null;
for (Reference ref : references) {
if (scope == null) {
scope = ref.scope;
} else
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> if (scope != ref.scope) {
return true;
}
}
return false;
}
/**
* @param index The index into the references array to look for an
* assigning declaration.
*
* This is either the declaration if a value is assigned (such as
* "var a = 2", "function a()...", "... catch (a)...").
*/
private boolean isInitializingDeclarationAt(int index) {
Reference maybeInit = references.get(index);
if (maybeInit.isInitializingDeclaration()) {
// This is a declaration that represents the initial value.
// Specifically, var declarations without assignments such as "var a;"
// are not.
return true;
}
return false;
}
/**
* @param index The index into the references array to look for an
* initialized assignment reference. That is, an assignment immediately
* follow a variable declaration that itself does not initialize the
* variable.
*/
private boolean isInitializingAssignmentAt(int index) {
if (index < references.size() && index > 0) {
Reference maybeDecl = references.get(index-1);
if (maybeDecl.isVarDeclaration()) {
Preconditions.checkState(!maybeDecl.isInitializingDeclaration());
Reference maybeInit = references.get(index);
if (maybeInit.isSimpleAssignmentToName()) {
return true;
}
}
}
return false;
}
/**
* @return The reference that provides the value for the variable at the
* time of the first read, if known, otherwise null.
*
* This is either the variable declaration ("var a = ...") or first
* reference following the declaration if it is an assignment.
*/
Reference getInitializingReference() {
if (isInitializingDeclarationAt(0)) {
return references.get(0);
} else if (isInitializingAssignmentAt(1)) {
return references.get(1);
}
return null;
}
/**
* Constants are allowed to be defined after their first use.
*/
Reference getInitializingReferenceForConstants() {
int size = references.size();
for (int i = 0; i < size; i++) {
if (isInitializingDeclarationAt(i) || isInitializingAssignmentAt(i)) {
return references.get(i);
}
}
return null;
}
/**
* @return Whether the variable is only assigned a value once for its
* lifetime.
*/
boolean isAssignedOnceInLifetime() {
Reference ref = getOneAndOnlyAssignment();
if (ref == null) {
return false;
}
// Make sure this assignment is not in a loop.
for (BasicBlock block = ref.getBasicBlock();
block != null; block = block.getParent()) {
if (block.isFunction) {
break;
} else if (block.isLoop) {
return false;
}
}
return true;
}
/**
* @return The one and only assignment. Returns if there are 0 or 2+
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
* assignments.
*/
private Reference getOneAndOnlyAssignment() {
Reference assignment = null;
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
if (assignment == null) {
assignment = ref;
} else {
return null;
}
}
}
return assignment;
}
/**
* @return Whether the variable is never assigned a value.
*/
boolean isNeverAssigned() {
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
return false;
}
}
return true;
}
boolean firstReferenceIsAssigningDeclaration() {
int size = references.size();
if (size > 0 && references.get(0).isInitializingDeclaration()) {
return true;
}
return false;
}
}
/**
* Represents a single declaration or reference to a variable.
*/
static final class Reference {
private static final Set<Integer> DECLARATION_PARENTS =
ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH);
private final Node nameNode;
private final Node parent;
private final Node grandparent;
private final BasicBlock basicBlock;
private final Scope scope;
private final String sourceName;
Reference(Node nameNode, Node parent, NodeTraversal t,
BasicBlock basicBlock) {
this(nameNode, parent, parent.getParent(), basicBlock, t.getScope(),
t.getSourceName());
}
// Bleeding functions are weird, because the declaration does
// not appear inside their scope. So they need their own constructor.
static Reference newBleedingFunction(NodeTraversal t,
BasicBlock basicBlock, Node func) {
return new Reference(func.getFirstChild(), func, func.getParent(),
basicBlock, t.getScope(), t.getSourceName());
}
private Reference(Node nameNode, Node parent, Node grandparent,
BasicBlock basicBlock, Scope scope, String sourceName) {
this.nameNode = nameNode;
this.parent = parent;
this.grandparent = grandparent;
this.basicBlock = basicBlock;
this.scope = scope;
this.sourceName = sourceName;
}
boolean isDeclaration() {
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.getType() == Token.LP &&
grandparent.getType() == Token.FUNCTION;
}
boolean isVarDeclaration() {
return parent.getType() == Token.VAR;
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(parent);
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
(parent.getType() != Token.VAR || nameNode.getFirstChild() != null);
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
return (parent.getType() == Token.FUNCTION)
? parent : NodeUtil.getAssignedValue(getNameNode());
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return parent;
}
Node getNameNode() {
return nameNode;
}
Node getGrandparent() {
return grandparent;
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.getType() == Token.VAR) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
return parent.getType() == Token.ASSIGN
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
public String getSourceName() {
return sourceName;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.getType() == Token.FUNCTION;
if (root.getParent() != null) {
int pType = root.getParent().getType();
this.isLoop = pType == Token.DO ||
pType == Token.WHILE ||
pType == Token.FOR;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ReverseAbstractInterpreter nextLink;
/**
* Constructs an interpreter, which is the only link in a chain. Interpreters
* can be appended using {@link #append}.
*/
ChainableReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
Preconditions.checkNotNull(convention);
this.convention = convention;
this.typeRegistry = typeRegistry;
firstLink = this;
nextLink = null;
}
/**
* Appends a link to {@code this}, returning the updated last link.
* <p>
* The pattern {@code new X().append(new Y())...append(new Z())} forms a
* chain starting with X, then Y, then ... Z.
* @param lastLink a chainable interpreter, with no next link
* @return the updated last link
*/
ChainableReverseAbstractInterpreter append(
ChainableReverseAbstractInterpreter lastLink) {
Preconditions.checkArgument(lastLink.nextLink == null);
this.nextLink = lastLink;
lastLink.firstLink = this.firstLink;
return lastLink;
}
/**
* Gets the first link of this chain.
*/
ChainableReverseAbstractInterpreter getFirst() {
return firstLink;
}
/**
* Calculates the preciser scope starting with the first link.
*/
protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return firstLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
/**
* Delegates the calculation of the preciser scope to the next link.
* If there is no next link, returns the blind scope.
*/
protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome) : blindScope;
}
/**
* Returns the type of a node in the given scope if the node corresponds to a
* name whose type is capable of being refined.
* @return The current type of the node if it can be refined, null otherwise.
*/
JSType getTypeIfRefinable(Node node, FlowScope scope) {
switch (node.getType()) {
case Token.NAME:
StaticSlot<JSType> nameVar = scope.getSlot(node.getString());
if (nameVar != null) {
JSType nameVarType = nameVar.getType();
if (nameVarType == null) {
nameVarType = node.getJSType();
}
return nameVarType;
}
return null;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
if (qualifiedName == null) {
return null;
}
StaticSlot<JSType> propVar = scope.getSlot(qualifiedName);
JSType propVarType = null;
if (propVar != null) {
propVarType =
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> propVar.getType();
}
if (propVarType == null) {
propVarType = node.getJSType();
}
if (propVarType == null) {
propVarType = getNativeType(UNKNOWN_TYPE);
}
return propVarType;
}
return null;
}
/**
* Declares a refined type in {@code scope} for the name represented by
* {@code node}. It must be possible to refine the type of the given node in
* the given scope, as determined by {@link #getTypeIfRefinable}.
*/
protected void declareNameInScope(FlowScope scope, Node node, JSType type) {
switch (node.getType()) {
case Token.NAME:
scope.inferSlotType(node.getString(), type);
break;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
Preconditions.checkNotNull(qualifiedName);
JSType origType = node.getJSType();
origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType;
scope.inferQualifiedSlot(qualifiedName, origType, type);
break;
default:
throw new IllegalArgumentException("Node cannot be refined. \n" +
node.toStringTree());
}
}
/**
* @see #getRestrictedWithoutUndefined(JSType)
*/
private final Visitor<JSType> restrictUndefinedVisitor =
new Visitor<JSType>() {
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().equals(type)) {
return enumElementType;
} else {
return type;
}
}
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE,
STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE);
}
public JSType caseNoObjectType() {
return getNativeType(NO_OBJECT_TYPE);
}
public JSType caseNoType() {
return getNativeType(NO_TYPE);
}
public JSType caseBooleanType() {
return getNativeType(BOOLEAN_TYPE);
}
public JSType caseFunctionType(FunctionType type) {
return type;
}
public JSType caseNullType() {
return getNativeType(NULL_TYPE);
}
public JSType caseNumberType() {
return getNativeType(NUMBER_TYPE);
}
public JSType caseObjectType(ObjectType type) {
return type;
}
public JSType caseStringType() {
return getNativeType(STRING_TYPE);
}
public JSType caseUnionType(UnionType type) {
return type.getRestrictedUnion(getNativeType(VOID_TYPE));
}
public JSType caseUnknownType() {
return getNativeType(UNKNOWN_TYPE);
}
public JSType caseVoidType() {
return null;
}
};
/**
* @see #getRestrictedWithoutNull
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>,
CheckLevel reportUnknownTypes) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.reverseInterpreter = reverseInterpreter;
this.typeRegistry = typeRegistry;
this.topScope = topScope;
this.scopeCreator = scopeCreator;
this.reportMissingOverride = reportMissingOverride;
this.reportUnknownTypes = reportUnknownTypes;
this.inferJSDocInfo = new InferJSDocInfo(compiler);
}
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
reportMissingOverride, reportUnknownTypes);
}
TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
CheckLevel.WARNING, CheckLevel.OFF);
}
/** Turn on the missing property check. Returns this for easy chaining. */
TypeCheck reportMissingProperties(boolean report) {
reportMissingProperties = report;
return this;
}
/**
* Main entry point for this phase of processing. This follows the pattern for
* JSCompiler phases.
*
* @param externsRoot The root of the externs parse tree.
* @param jsRoot The root of the input parse tree to be checked.
*/
public void process(Node externsRoot, Node jsRoot) {
Preconditions.checkNotNull(scopeCreator);
Preconditions.checkNotNull(topScope);
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
if (externsRoot != null) {
check(externsRoot, true);
}
check(jsRoot, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final TypeCheck outerThis = this;
final Scope outerScope = t.getScope();
final FunctionType functionType = (FunctionType) n.getJSType();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.NAME:
typeable = visitName(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>t, n, parent);
break;
case Token.LP:
// If this is under a FUNCTION node, it is a parameter list and can be
// ignored here.
if (parent.getType() != Token.FUNCTION) {
ensureTyped(t, n, getJSType(n.getFirstChild()));
} else {
typeable = false;
}
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.REF_SPECIAL:
ensureTyped(t, n);
break;
case Token.GET_REF:
ensureTyped(t, n, getJSType(n.getFirstChild()));
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, NUMBER_TYPE);
} else {
typeable = false;
}
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.STRING:
if (n.getParent().getType() != Token.OBJECTLIT) {
ensureTyped(t, n, STRING_TYPE);
} else {
typeable = false;
}
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !NodeUtil.isExpressionNode(parent);
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
if (!childType.matchesInt32Context()) {
report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result =
leftTypeRestricted.testForEquality(rightTypeRestricted);
if (result != TernaryValue.UNKNOWN) {
if (n.getType() == Token.NE) {
result = result.not();
}
report(t, n, DETERMINISTIC_TEST, leftType.toString(),
rightType.toString(), result.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.SHEQ:
case Token.SHNE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
report(t, n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(),
rightType.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
if (rightType.isNumber()) {
validator.expectNumber(
t, n, leftType, "left side of numeric comparison");
} else if (leftType.isNumber()) {
validator.expectNumber(
t, n, rightType, "right side of numeric comparison");
} else if (leftType.matchesNumberContext() &&
rightType.matchesNumberContext()) {
// OK.
} else {
// Whether the comparison is numeric will be determined at runtime
// each time the expression is evaluated. Regardless, both operands
// should match a string context.
String message = "left side of comparison
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>";
validator.expectString(t, n, leftType, message);
validator.expectNotVoid(
t, n, leftType, message, getNativeType(STRING_TYPE));
message = "right side of comparison";
validator.expectString(t, n, rightType, message);
validator.expectNotVoid(
t, n, rightType, message, getNativeType(STRING_TYPE));
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.IN:
left = n.getFirstChild();
right = n.getLastChild();
leftType = getJSType(left);
rightType = getJSType(right);
validator.expectObject(t, n, rightType, "'in' requires an object");
validator.expectString(t, left, leftType, "left side of 'in'");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.INSTANCEOF:
left = n.getFirstChild();
right = n.getLastChild();
leftType = getJSType(left);
rightType = getJSType(right).restrictByNotNullOrUndefined();
validator.expectAnyObject(
t, left, leftType, "deterministic instanceof yields false");
validator.expectActualObject(
t, right, rightType, "instanceof requires an object");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.ASSIGN:
visitAssign(t, n);
typeable = false;
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
if (!isReference(n.getFirstChild())) {
report(t, n, BAD_DELETE);
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(
t, child, childType, "with
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.getType() == Token.OBJECTLIT)
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
checkNoTypeCheckSection(n, false);
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes.isOn()) {
compiler.report(
t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.getType() == Token.ASSIGN</code> is an implicit invariant)
*/
private void visitAssign(NodeTraversal t, Node assign) {
JSDoc
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Info info = assign.getJSDocInfo();
Node lvalue = assign.getFirstChild();
Node rvalue = assign.getLastChild();
if (lvalue.getType() == Token.GETPROP) {
Node object = lvalue.getFirstChild();
JSType objectJsType = getJSType(object);
String property = lvalue.getLastChild().getString();
// the first name in this getprop refers to an interface
// we perform checks in addition to the ones below
if (object.getType() == Token.GETPROP) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue);
}
}
// /** @type ... */object.name = ...;
if (info != null && info.hasType()) {
visitAnnotatedAssignGetprop(t, assign,
info.getType().evaluate(t.getScope(), typeRegistry), object,
property, rvalue);
return;
}
// /** @enum ... */object.name = ...;
if (info != null && info.hasEnumParameterType()) {
checkEnumInitializer(
t, rvalue, info.getEnumParameterType().evaluate(
t.getScope(), typeRegistry));
return;
}
// object.prototype = ...;
if (property.equals("prototype")) {
if (objectJsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) objectJsType;
if (functionType.isConstructor()) {
JSType rvalueType = rvalue.getJSType();
validator.expectObject(t, rvalue, rvalueType,
OVERRIDING_PROTOTYPE_WITH_NON_OBJECT);
}
} else {
// TODO(user): might want to flag that
}
return;
}
// object.prototype.property = ...;
if (object.getType() == Token.GETPROP) {
Node object2 = object.getFirstChild();
String property2 = NodeUtil.getStringValue(object.getLastChild());
if ("prototype".equals(property2)) {
JSType jsType = object2.getJSType();
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
if (functionType.isConstructor() || functionType.isInterface()) {
checkDeclaredPropertyInheritance(
t, assign, functionType, property, info, getJSType(rvalue));
}
} else {
// TODO(user): might want to flag that
}
return;
}
}
// object.property = ...;
ObjectType type = ObjectType.cast(
objectJsType.restrictByNotNullOrUndefined());
if (type != null) {
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
validator.expectCanAssignToPropertyOf(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> t, assign, getJSType(rvalue),
type.getPropertyType(property), object, property);
}
return;
}
} else if (lvalue.getType() == Token.NAME) {
// variable with inferred type case
JSType rvalueType = getJSType(assign.getLastChild());
Var var = t.getScope().getVar(lvalue.getString());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
}
}
// fall through case
JSType leftType = getJSType(lvalue);
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
/**
* Returns true if any type in the chain has an implictCast annotation for
* the given property.
*/
private boolean propertyIsImplicitCast(ObjectType type, String prop) {
for (; type != null; type = type.getImplicitPrototype()) {
JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isImplicitCast()) {
return true;
}
}
return false;
}
/**
* Given a constructor type and a property name, check that the property has
* the JSDoc annotation @override iff the property is declared on a
* superclass. Several checks regarding inheritance correctness are also
* performed.
*/
private void checkDeclaredPropertyInheritance(
NodeTraversal t, Node n, FunctionType ctorType, String propertyName,
JSDocInfo info, JSType propertyType) {
// TODO(user): We're not 100% confident that type-checking works,
// so we return quietly if the unknown type is a superclass of this type.
// Remove this check as we become more confident. We should flag a warning
// when the unknown type is on the inheritance chain, as it is likely
// because of a programmer error.
if (ctorType.hasUnknownSupertype()) {
return;
}
FunctionType superClass = ctorType.getSuperClassConstructor();
boolean superClassHasProperty = superClass != null &&
superClass.getPrototype().hasProperty(propertyName);
boolean declaredOverride = info != null && info.isOverride();
boolean foundInterfaceProperty = false;
if (ctorType.isConstructor()) {
for (JSType implementedInterface : ctorType.getImplementedInterfaces()) {
if (implementedInterface.isUnknownType()) {
continue;
}
FunctionType interfaceType =
implementedInterface.toObjectType().getConstructor();
boolean interfaceHasProperty =
interfaceType.getPrototype().hasProperty(propertyName);
foundInterfaceProperty = foundInterfaceProperty || interfaceHasProperty;
if (reportMissingOverride.isOn() && !declaredOverride &&
interfaceHasProperty) {
// @override
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().getType() == Token.FUNCTION
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* object.property = ...;
* </pre>
* that have an {@code @type} annotation.
*/
private void visitAnnotatedAssignGetprop(NodeTraversal t,
Node assign, JSType type, Node object, String property, Node rvalue) {
// verifying that the rvalue has the correct type
validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type,
object, property);
}
/**
* Visits a NAME node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
* @return whether the node is typeable or not
*/
boolean visitName(NodeTraversal t, Node n, Node parent) {
// At this stage, we need to determine whether this is a leaf
// node in an expression (which therefore needs to have a type
// assigned for it) versus some other decorative node that we
// can safely ignore. Function names, arguments (children of LP nodes) and
// variable declarations are ignored.
// TODO(user): remove this short-circuiting in favor of a
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.LP ||
parentNodeType == Token.VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// GETPROP nodes have an assigned type on their node by the scope creator
// if this is an enum declaration. The only namespaced enum declarations
// that we allow are of the form object.name = ...;
if (n.getJSType() != null && parent.getType() == Token.ASSIGN) {
return;
}
// obj.prop or obj.method()
// Lots of types can appear on the left, a call to a void function can
// never be on the left. getPropertyType will decide what is acceptable
// and what isn't.
Node property = n.getLastChild();
Node objNode = n.getFirstChild();
JSType childType = getJSType(objNode);
// TODO(user): remove in favor of flagging every property access on
// non-object.
if (!validator.expectNotVoid(t, n, childType,
"undefined has no properties", getNativeType(OBJECT_TYPE))) {
ensureTyped(t, n);
return;
}
checkPropertyAccess(childType, property.getString(), t, n);
ensureTyped(t, n);
}
/**
* Make sure that the access of this property is ok.
*/
private void checkPropertyAccess(JSType childType, String propName,
NodeTraversal t, Node n) {
ObjectType objectType = childType.dereference();
if (objectType != null) {
JSType propType = getJSType(n);
if ((!objectType.hasProperty(propName) ||
objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) &&
propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
report(t, n, INEXISTENT_ENUM_ELEMENT, propName);
} else if (!objectType.isEmptyType() &&
reportMissingProperties && !isPropertyTest(n)) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
report(t, n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirstChild(), true));
}
}
}
} else {
// TODO(nicksantos): might want to flag the access on a non object when
// it's impossible to get a property from this type.
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().getType() == Token.OR &&
parent.getParent().getFirstChild() == parent;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
validator.expectIndexMatch(t, n, getJSType(left), getJSType(right));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation logic.
Var var = t.getScope().getVar(name.getString());
if (value != null) {
JSType valueType = getJSType(value);
JSType nameType = var.getType();
nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType;
JSDocInfo info = name.getJSDocInfo();
if (info == null) {
info = varInfo;
}
if (info != null && info.hasEnumParameterType()) {
// var.getType() can never be null, this would indicate a bug in the
// scope creation logic.
checkEnumInitializer(
t, value,
info.getEnumParameterType().evaluate(t.getScope(), typeRegistry));
} else if (var.isTypeInferred()) {
ensureTyped(t, name, valueType);
} else {
validator.expectCanAssignTo(
t, value, valueType, nameType, "initializing variable");
}
}
}
}
/**
* Visits a NEW node.
*/
private void visitNew(NodeTraversal t, Node n) {
Node constructor = n.getFirstChild();
FunctionType type = getFunctionType(constructor);
if (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>type != null && type.isConstructor()) {
visitParameterList(t, n, type);
ensureTyped(t, n, type.getInstanceType());
} else {
// TODO(user): add support for namespaced objects.
if (constructor.getType() != Token.GETPROP) {
// TODO(user): make the constructor node have lineno/charno
// and use constructor for a more precise error indication.
// It seems that GETPROP nodes are missing this information.
Node line;
if (constructor.getLineno() < 0 || constructor.getCharno() < 0) {
line = n;
} else {
line = constructor;
}
report(t, line, NOT_A_CONSTRUCTOR);
}
ensureTyped(t, n);
}
}
/**
* Visits a {@link Token#FUNCTION} node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitFunction(NodeTraversal t, Node n) {
JSDocInfo info = n.getJSDocInfo();
FunctionType functionType = (FunctionType) n.getJSType();
String functionPrivateName = n.getFirstChild().getString();
if (functionType.isInterface() || functionType.isConstructor()) {
FunctionType baseConstructor = functionType.
getPrototype().getImplicitPrototype().getConstructor();
if (baseConstructor != null &&
baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) &&
(baseConstructor.isConstructor() && functionType.isInterface() ||
baseConstructor.isInterface() && functionType.isConstructor())) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE, functionPrivateName));
}
for (JSType baseInterface : functionType.getImplementedInterfaces()) {
boolean badImplementedType = false;
ObjectType baseInterfaceObj = ObjectType.cast(baseInterface);
if (baseInterfaceObj != null) {
FunctionType interfaceConstructor =
baseInterfaceObj.getConstructor();
if (interfaceConstructor != null &&
!interfaceConstructor.isInterface()) {
badImplementedType = true;
}
} else {
badImplementedType = true;
}
if (badImplementedType) {
report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName);
}
}
if (functionType.isConstructor()) {
validator.expectAllInterfacePropertiesImplemented(functionType);
}
}
}
/**
* Visits a CALL node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitCall(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
JSType childType = getJSType(child).restrictByNotNullOr
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> will fail to compile,
// so let it go.
if (function == null) {
return;
}
JSType jsType = getJSType(function);
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
JSType returnType = functionType.getReturnType();
// if no return type is specified, undefined must be returned
// (it's a void function)
if (returnType == null) {
returnType = getNativeType(VOID_TYPE);
}
// fetching the returned value's type
Node valueNode = n.getFirstChild();
JSType actualReturnType;
if (valueNode == null) {
actualReturnType = getNativeType(VOID_TYPE);
valueNode = n;
} else {
actualReturnType = getJSType(valueNode);
}
// verifying
validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
if (!leftType.matchesInt32Context()) {
report(t, left, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
report(t, right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
break;
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right, rightType, "right operand");
break;
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
validator.expectBitwiseable(t, left, leftType,
"bad left operand to bitwise operator");
validator.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>expectBitwiseable(t, right, rightType,
"bad right operand to bitwise operator");
break;
case Token.ASSIGN_ADD:
case Token.ADD:
break;
default:
report(t, n, UNEXPECTED_TOKEN, Node.tokenToName(op));
}
ensureTyped(t, n);
}
/**
* <p>Checks the initializer of an enum. An enum can be initialized with an
* object literal whose values must be subtypes of the declared enum element
* type, or by copying another enum.</p>
*
* <p>In the case of an enum copy, we verify that the enum element type of the
* enum used for initialization is a subtype of the enum element type of
* the enum the value is being copied in.</p>
*
* <p>Examples:</p>
* <pre>var myEnum = {FOO: ..., BAR: ...};
* var myEnum = myOtherEnum;</pre>
*
* @param value the value used for initialization of the enum
* @param primitiveType The type of each element of the enum.
*/
private void checkEnumInitializer(
NodeTraversal t, Node value, JSType primitiveType) {
if (value.getType() == Token.OBJECTLIT) {
for (Node key = value.getFirstChild();
key != null; key = key.getNext()) {
Node propValue = key.getFirstChild();
// the value's type must be assignable to the enum's primitive type
validator.expectCanAssignTo(
t, propValue, getJSType(propValue), primitiveType,
"element type must match enum's type");
}
} else if (value.getJSType() instanceof EnumType) {
// TODO(user): Remove the instanceof check in favor
// of a type.isEnumType() predicate. Currently, not all enum types are
// implemented by the EnumClass, e.g. the unknown type and the any
// type. The types need to be defined by interfaces such that an
// implementation can implement multiple types interface.
EnumType valueEnumType = (EnumType) value.getJSType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
primitiveType, "incompatible enum element types");
} else {
// The error condition is handled in TypedScopeCreator.
}
}
/**
* This predicate is used to determine if the node represents an expression
* that is a Reference according to JavaScript definitions.
*
* @param n The node being checked.
* @return true if the sub-tree n is a reference, false otherwise.
*/
private static boolean isReference(Node n) {
switch (n.getType()) {
case Token.GETELEM:
case Token.GETPROP:
case Token.NAME:
return true;
default:
return false;
}
}
/**
* This method gets the JSType from the Node argument
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
/**
* Gets the type of the node or {@code null} if the node's type is not a
* function.
*/
private FunctionType getFunctionType(Node n) {
JSType type = getJSType(n).restrictByNotNullOrUndefined();
if (type.isUnknownType()) {
return typeRegistry.getNativeFunctionType(U2U_CONSTRUCTOR_TYPE);
} else if (type instanceof FunctionType) {
return (FunctionType) type;
} else {
return null;
}
}
// TODO(nicksantos): TypeCheck should never be attaching types to nodes.
// All types should be attached by TypeInference. This is not true today
// for legacy reasons. There are a number of places where TypeInference
// doesn't attach a type, as a signal to TypeCheck that it needs to check
// that node's type.
/**
* Ensure that the given node has a type. If it does not have one,
* attach the UNKNOWN_TYPE.
*/
private void ensureTyped(NodeTraversal t, Node n) {
ensureTyped(t, n, getNativeType(UNKNOWN_TYPE));
}
private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) {
ensureTyped(t, n, getNativeType(type));
}
/**
* Enforces type casts, and ensures the node is typed.
*
* A cast in the way that we use it in JSDoc annotations never
* alters the generated code and therefore never can induce any runtime
* operation. What this means is that a 'cast' is really just a compile
* time constraint on the underlying value. In the future, we may add
* support for run-time casts for compiled tests.
*
* To ensure some shred of sanity, we enforce the notion that the
* type you are casting to may only meaningfully be a narrower type
* than the underlying declared type. We also invalidate optimizations
* on bad type casts.
*
* @param t The traversal object needed to report errors.
* @param n The node getting a type assigned to it.
* @param type The type to be assigned.
*/
private void ensureTyped(NodeTraversal t, Node n, JSType type) {
// Make sure FUNCTION nodes always get function type.
Preconditions.checkState(n.getType() != Token.FUNCTION ||
type instanceof FunctionType ||
type.isUnknownType());
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
if (info.hasType()) {
JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry);
validator.expectCanCast(t, n, infoType, type);
type = infoType;
}
if (info.isImplicitCast() && !inExterns) {
String propName = n.getType() == Token.GETPROP ?
n.getLastChild().getString() : "(missing)";
compiler.report(
t.makeError(n, ILLEGAL_IMPLICIT_CAST, propName));
}
}
if (n.getJSType() == null) {
n.setJSType(type);
}
}
/**
* Returns the percentage of nodes typed by the type checker.
* @return a number between 0.0 and 100.0
*/
double getTypedPercent() {
int total = nullCount + unknownCount + typedCount;
if (total == 0) {
return 0.0;
} else {
return (100.0 * typedCount) / total;
}
}
private JSType getNativeType(JSTypeNative typeId) {
return typeRegistry.getNativeType(typeId);
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> will
* become the super node.
*/
public N getPartitionSuperNode(N node) {
Preconditions.checkNotNull(colorToNodeMap,
"No coloring founded. color() should be called first.");
Color color = graph.getNode(node).getAnnotation();
N headNode = colorToNodeMap[color.value];
if (headNode == null) {
colorToNodeMap[color.value] = node;
return node;
} else {
return headNode;
}
}
public AdjacencyGraph<N, E> getGraph() {
return graph;
}
public static class Color implements Annotation {
int value = 0;
Color(int value) {
this.value = value;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Color)) {
return false;
} else {
return value == ((Color) other).value;
}
}
@Override
public int hashCode() {
return value;
}
}
/**
* Greedily assign nodes with high degree unique colors.
*/
public static class GreedyGraphColoring<N, E> extends GraphColoring<N, E> {
private final Comparator<N> tieBreaker;
public GreedyGraphColoring(AdjacencyGraph<N, E> graph) {
this(graph, null);
}
/**
* @param tieBreaker In case of a tie between two nodes of the same degree,
* this comparator will determine which node should be colored first.
*/
public GreedyGraphColoring(
AdjacencyGraph<N, E> graph, Comparator<N> tieBreaker) {
super(graph);
this.tieBreaker = tieBreaker;
}
@Override
public int color() {
graph.clearNodeAnnotations();
List<GraphNode<N, E>> worklist = Lists.newArrayList(graph.getNodes());
// Sort nodes by degree.
Collections.sort(worklist, new Comparator<GraphNode<N, E>>() {
@Override
public int compare(GraphNode<N, E> o1, GraphNode<N, E> o2) {
int result = graph.getWeight(o2.getValue())
- graph.getWeight(o1.getValue());
return result == 0 && tieBreaker != null ?
tieBreaker.compare(o1.getValue(), o2.getValue()) : result;
}
});
// Idea: From the highest to lowest degree, assign any uncolored node with
// a unique color if none of its neighbor has been assigned that color.
int count = 0;
do {
Color color = new Color(count);
SubGraph<N, E> subgraph = graph.newSubGraph();
for (Iterator<GraphNode<N, E>> i = worklist.iterator(); i.hasNext();) {
GraphNode<N, E> node = i.next();
if (subgraph.isIndependentOf(node.getValue())) {
subgraph.addNode(node.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
/**
* A factory for creating JSCompiler passes based on the Options
* injected. Contains all meta-data about compiler passes (like
* whether it can be run multiple times, a human-readable name for
* logging, etc.).
*
* @author nicksantos@google.com (Nick Santos)
*/
public abstract class PassFactory {
private final String name;
private final boolean isOneTimePass;
private boolean isCreated = false;
/**
* @param name The name of the pass that this factory creates.
* @param isOneTimePass If true, the pass produced by this factory can
* only be run once.
*/
protected PassFactory(String name, boolean isOneTimePass) {
this.name = name;
this.isOneTimePass = isOneTimePass;
}
/**
* @return The name of this pass.
*/
String getName() {
return name;
}
/**
* @return Whether the pass produced by this factory can only be run once.
*/
boolean isOneTimePass() {
return isOneTimePass;
}
/**
* Make a new pass factory that only creates one-time passes.
*/
PassFactory makeOneTimePass() {
if (isOneTimePass()) {
return this;
}
final PassFactory self = this;
return new PassFactory(name, true /* one time pass */) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return self.createInternal(compiler);
}
};
}
/**
* Creates a new compiler pass to be run.
*/
final CompilerPass create(AbstractCompiler compiler) {
Preconditions.checkState(!isCreated || !isOneTimePass,
"One-time passes cannot be run multiple times: " + name);
isCreated = true;
return createInternal(compiler);
}
/**
* Creates a new compiler pass to be run.
*/
abstract protected CompilerPass createInternal(AbstractCompiler compiler);
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.rhino.ast.Label;
import com.google.javascript.jscomp.mozilla.rhino.ast.LabeledStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.Name;
import com.google.javascript.jscomp.mozilla.rhino.ast.NewExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.NumberLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectProperty;
import com.google.javascript.jscomp.mozilla.rhino.ast.ParenthesizedExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.PropertyGet;
import com.google.javascript.jscomp.mozilla.rhino.ast.RegExpLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ReturnStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.Scope;
import com.google.javascript.jscomp.mozilla.rhino.ast.StringLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchCase;
import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.ThrowStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.TryStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.UnaryExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.VariableDeclaration;
import com.google.javascript.jscomp.mozilla.rhino.ast.VariableInitializer;
import com.google.javascript.jscomp.mozilla.rhino.ast.WhileLoop;
import com.google.javascript.jscomp.mozilla.rhino.ast.WithStatement;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
/**
* IRFactory transforms the new AST to the old AST.
*
*/
public class IRFactory {
private final String sourceString;
private final String sourceName;
private final Config config;
private final ErrorReporter errorReporter;
private final TransformDispatcher transformDispatcher;
// non-static for thread safety
private final Set<String> ALLOWED_DIRECTIVES = Sets.newHashSet("use strict");
// @license text gets appended onto the fileLevelJsDocBuilder as found,
// and stored in JSDocInfo for placeholder node.
Node rootNodeJsDocHolder = new Node(Token.SCRIPT);
Node.FileLevelJsDocBuilder fileLevelJsDocBuilder =
rootNodeJsDocHolder.getJsDocBuilderForNode();
JSDocInfo fileOverviewInfo =
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> null;
// Use a template node for properties set on all nodes to minimize the
// memory footprint associated with these.
private Node templateNode;
// TODO(johnlenz): Consider creating a template pool for ORIGINALNAME_PROP.
private IRFactory(String sourceString,
String sourceName,
Config config,
ErrorReporter errorReporter) {
this.sourceString = sourceString;
this.sourceName = sourceName;
this.config = config;
this.errorReporter = errorReporter;
this.transformDispatcher = new TransformDispatcher();
// The template node properties are applied to all nodes in this transform.
this.templateNode = createTemplateNode();
}
// Create a template node to use as a source of common attributes, this allows
// the prop structure to be shared among all the node from this source file.
// This reduces the cost of these properties to O(nodes) to O(files).
private Node createTemplateNode() {
// The Node type choice is arbitrary.
Node templateNode = new Node(Token.SCRIPT);
templateNode.putProp(Node.SOURCENAME_PROP, sourceName);
return templateNode;
}
public static Node transformTree(AstRoot node,
String sourceString,
Config config,
ErrorReporter errorReporter) {
IRFactory irFactory = new IRFactory(sourceString, node.getSourceName(),
config, errorReporter);
Node irNode = irFactory.transform(node);
if (node.getComments() != null) {
for (Comment comment : node.getComments()) {
if (comment.getCommentType() == JSDOC && !comment.isParsed()) {
irFactory.handlePossibleFileOverviewJsDoc(comment);
}
}
}
irFactory.setFileOverviewJsDoc(irNode);
return irNode;
}
private void setFileOverviewJsDoc(Node irNode) {
// Only after we've seen all @fileoverview entries, attach the
// last one to the root node, and copy the found license strings
// to that node.
irNode.setJSDocInfo(rootNodeJsDocHolder.getJSDocInfo());
if (fileOverviewInfo != null) {
if ((irNode.getJSDocInfo() != null) &&
(irNode.getJSDocInfo().getLicense() != null)) {
fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense());
}
irNode.setJSDocInfo(fileOverviewInfo);
}
}
private Node transformBlock(AstNode node) {
Node irNode = transform(node);
if (irNode.getType() != Token.BLOCK) {
if (irNode.getType() == Token.EMPTY) {
irNode.setType(Token.BLOCK);
irNode.setWasEmptyNode(true);
} else {
Node newBlock = newNode(Token.BLOCK, irNode);
newBlock.setLineno(irNode.getLineno());
newBlock.setCharno(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>irNode.getCharno());
irNode = newBlock;
}
}
return irNode;
}
/**
* @return true if the jsDocParser represents a fileoverview.
*/
private boolean handlePossibleFileOverviewJsDoc(
JsDocInfoParser jsDocParser) {
if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) {
fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo();
return true;
}
return false;
}
private void handlePossibleFileOverviewJsDoc(Comment comment) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment);
comment.setParsed(true);
handlePossibleFileOverviewJsDoc(jsDocParser);
}
private JSDocInfo handleJsDoc(AstNode node) {
Comment comment = node.getJsDocNode();
if (comment != null) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment);
comment.setParsed(true);
if (!handlePossibleFileOverviewJsDoc(jsDocParser)) {
return jsDocParser.retrieveAndResetParsedJSDocInfo();
}
}
return null;
}
private Node transform(AstNode node) {
JSDocInfo jsDocInfo = handleJsDoc(node);
Node irNode = justTransform(node);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
// If we have a named function, set the position to that of the name.
if (irNode.getType() == Token.FUNCTION &&
irNode.getFirstChild().getLineno() != -1) {
irNode.setLineno(irNode.getFirstChild().getLineno());
irNode.setCharno(irNode.getFirstChild().getCharno());
} else {
if (irNode.getLineno() == -1) {
// If we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
}
}
return irNode;
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual JSDoc comments and for handling
* file-level JSDoc comments (@fileoverview and @license).
*
* @param node The JsDoc Comment node to parse.
* @return A JSDocInfoParser. Will contain either fileoverview jsdoc, or
* normal jsdoc, or no jsdoc (if the method parses to the wrong level).
*/
private JsDocInfoParser createJsDocInfoParser(Comment node) {
String comment = node.getValue();
int
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> lineno = node.getLineno();
int position = node.getAbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser jsdocParser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
sourceName,
config,
errorReporter);
jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder);
jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo);
jsdocParser.parse();
return jsdocParser;
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.jscomp.mozilla.rhino.Node n) {
Node node = newNode(transformTokenType(n.getType()));
for (com.google.javascript.jscomp.mozilla.rhino.Node child : n) {
node.addChildToBack(transform((AstNode)child));
}
return node;
}
/**
* Transforms the given node and then sets its type to Token.STRING if it
* was Token.NAME. If its type was already Token.STRING, then quotes it.
* Used for properties, as the old AST uses String tokens, while the new one
* uses Name tokens for unquoted strings. For example, in
* var o = {'a' : 1, b: 2};
* the string 'a' is quoted, while the name b is turned into a string, but
* unquoted.
*/
private Node transformAsString(AstNode n) {
Node ret = transform(n);
if (ret.getType() == Token.STRING) {
ret.putBooleanProp(Node.QUOTED_PROP, true);
} else if (ret.getType() == Token.NAME) {
ret.setType(Token.STRING);
}
return ret;
}
@Override
Node processArrayLiteral(ArrayLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.ARRAYLIT);
int skipCount = 0;
for (AstNode child : literalNode.getElements()) {
Node c = transform(child);
if (c.getType() == Token.EMPTY) {
skipCount++;
}
node.addChildToBack(c);
}
if (skipCount > 0) {
int[] skipIndexes = new int[skipCount];
int
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> i = 0;
int j = 0;
for (Node child : node.children()) {
if (child.getType() == Token.EMPTY) {
node.removeChild(child);
skipIndexes[j] = i;
j++;
}
i++;
}
node.putProp(Node.SKIP_INDEXES_PROP, skipIndexes);
}
return node;
}
@Override
Node processAssignment(Assignment assignmentNode) {
return processInfixExpression(assignmentNode);
}
@Override
Node processAstRoot(AstRoot rootNode) {
Node node = newNode(Token.SCRIPT);
for (com.google.javascript.jscomp.mozilla.rhino.Node child : rootNode) {
node.addChildToBack(transform((AstNode)child));
}
parseDirectives(node);
return node;
}
/**
* Parse the directives, encode them in the AST, and remove their nodes.
*
* For information on ES5 directives, see section 14.1 of
* Ecma-262, Edition 5.
*
* It would be nice if Rhino would eventually take care of this for
* us, but right now their directive-processing is a one-off.
*/
private void parseDirectives(Node node) {
// Remove all the directives, and encode them in the AST.
Set<String> directives = null;
while (isDirective(node.getFirstChild())) {
String directive = node.removeFirstChild().getFirstChild().getString();
if (directives == null) {
directives = Sets.newHashSet(directive);
} else {
directives.add(directive);
}
}
if (directives != null) {
node.setDirectives(directives);
}
}
private boolean isDirective(Node n) {
if (n == null) return false;
int nType = n.getType();
return (nType == Token.EXPR_RESULT || nType == Token.EXPR_VOID) &&
n.getFirstChild().getType() == Token.STRING &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = newNode(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = newNode(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null) {
errorReporter.error(
"Catch clauses are not supported",
sourceName,
clauseNode.getC
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>atchCondition().getLineno(), "", 0);
}
node.addChildToBack(transformBlock(clauseNode.getBody()));
return node;
}
@Override
Node processConditionalExpression(ConditionalExpression exprNode) {
return newNode(
Token.HOOK,
transform(exprNode.getTestExpression()),
transform(exprNode.getTrueExpression()),
transform(exprNode.getFalseExpression()));
}
@Override
Node processContinueStatement(ContinueStatement statementNode) {
Node node = newNode(Token.CONTINUE);
if (statementNode.getLabel() != null) {
Node labelName = transform(statementNode.getLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processDoLoop(DoLoop loopNode) {
return newNode(
Token.DO,
transformBlock(loopNode.getBody()),
transform(loopNode.getCondition()));
}
@Override
Node processElementGet(ElementGet getNode) {
return newNode(
Token.GETELEM,
transform(getNode.getTarget()),
transform(getNode.getElement()));
}
@Override
Node processEmptyExpression(EmptyExpression exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processExpressionStatement(ExpressionStatement statementNode) {
Node node = newNode(transformTokenType(statementNode.getType()));
node.addChildToBack(transform(statementNode.getExpression()));
return node;
}
@Override
Node processForInLoop(ForInLoop loopNode) {
return newNode(
Token.FOR,
transform(loopNode.getIterator()),
transform(loopNode.getIteratedObject()),
transformBlock(loopNode.getBody()));
}
@Override
Node processForLoop(ForLoop loopNode) {
Node node = newNode(
Token.FOR,
transform(loopNode.getInitializer()),
transform(loopNode.getCondition()),
transform(loopNode.getIncrement()));
node.addChildToBack(transformBlock(loopNode.getBody()));
return node;
}
@Override
Node processFunctionCall(FunctionCall callNode) {
Node node = newNode(transformTokenType(callNode.getType()),
transform(callNode.getTarget()));
for (AstNode child : callNode.getArguments()) {
node.addChildToBack(transform(child));
}
int leftParamPos = callNode.getAbsolutePosition() + callNode.getLp();
node.setLineno(callNode.getLineno());
node.setCharno(position2charno(leftParamPos));
return node;
}
@Override
Node processFunctionNode(FunctionNode functionNode) {
Name name = functionNode.getFunctionName();
Boolean isUnnamedFunction = false;
if (name == null) {
name = new Name();
name.setIdentifier("");
isUnnamedFunction = true;
}
Node node = newNode(Token.FUNCTION);
Node newName =
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> transform(name);
if (isUnnamedFunction) {
// Old Rhino tagged the empty name node with the line number of the
// declaration.
newName.setLineno(functionNode.getLineno());
// TODO(bowdidge) Mark line number of paren correctly.
// Same problem as below - the left paren might not be on the
// same line as the function keyword.
int lpColumn = functionNode.getAbsolutePosition() +
functionNode.getLp();
newName.setCharno(position2charno(lpColumn));
}
node.addChildToBack(newName);
Node lp = newNode(Token.LP);
// The left paren's complicated because it's not represented by an
// AstNode, so there's nothing that has the actual line number that it
// appeared on. We know the paren has to appear on the same line as the
// function name (or else a semicolon will be inserted.) If there's no
// function name, assume the paren was on the same line as the function.
// TODO(bowdidge): Mark line number of paren correctly.
Name fnName = functionNode.getFunctionName();
if (fnName != null) {
lp.setLineno(fnName.getLineno());
} else {
lp.setLineno(functionNode.getLineno());
}
int lparenCharno = functionNode.getLp() +
functionNode.getAbsolutePosition();
lp.setCharno(position2charno(lparenCharno));
for (AstNode param : functionNode.getParams()) {
lp.addChildToBack(transform(param));
}
node.addChildToBack(lp);
Node bodyNode = transform(functionNode.getBody());
parseDirectives(bodyNode);
node.addChildToBack(bodyNode);
return node;
}
@Override
Node processIfStatement(IfStatement statementNode) {
Node node = newNode(Token.IF);
node.addChildToBack(transform(statementNode.getCondition()));
node.addChildToBack(transformBlock(statementNode.getThenPart()));
if (statementNode.getElsePart() != null) {
node.addChildToBack(transformBlock(statementNode.getElsePart()));
}
return node;
}
@Override
Node processInfixExpression(InfixExpression exprNode) {
Node n = newNode(
transformTokenType(exprNode.getType()),
transform(exprNode.getLeft()),
transform(exprNode.getRight()));
// Set the line number here so we can fine-tune it in ways transform
// doesn't do.
n.setLineno(exprNode.getLineno());
// Position in new ASTNode is to start of expression, but old-fashioned
// line numbers from Node reference the operator token. Add the offset
// to the operator to get the correct character number.
n.setCharno(position2charno(exprNode.getAbsolutePosition() +
exprNode.get
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>OperatorPosition()));
return n;
}
@Override
Node processKeywordLiteral(KeywordLiteral literalNode) {
return newNode(transformTokenType(literalNode.getType()));
}
@Override
Node processLabel(Label labelNode) {
return newStringNode(Token.LABEL_NAME, labelNode.getName());
}
@Override
Node processLabeledStatement(LabeledStatement statementNode) {
Node node = newNode(Token.LABEL);
Node prev = null;
Node cur = node;
for (Label label : statementNode.getLabels()) {
if (prev != null) {
prev.addChildToBack(cur);
}
cur.addChildToBack(transform(label));
cur.setLineno(label.getLineno());
int clauseAbsolutePosition =
position2charno(label.getAbsolutePosition());
cur.setCharno(clauseAbsolutePosition);
prev = cur;
cur = newNode(Token.LABEL);
}
prev.addChildToBack(transform(statementNode.getStatement()));
return node;
}
@Override
Node processName(Name nameNode) {
return newStringNode(Token.NAME, nameNode.getIdentifier());
}
@Override
Node processNewExpression(NewExpression exprNode) {
return processFunctionCall(exprNode);
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (!config.acceptES5) {
if (el.isGetter()) {
reportGetter(el);
continue;
} else if (el.isSetter()) {
reportSetter(el);
continue;
}
}
Node key = transformAsString(el.getLeft());
if (el.isGetter()) {
key.setType(Token.GET);
} else if (el.isSetter()) {
key.setType(Token.SET);
}
key.addChildToFront(transform(el.getRight()));
node.addChildToBack(key);
}
return node;
}
@Override
Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
return newNode(
Token.GETPROP,
transform(getNode.getTarget()),
transformAsString(getNode.getProperty()));
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode = newStringNode(literalNode.getValue());
// assume it's on the same
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> line.
literalStringNode.setLineno(literalNode.getLineno());
Node node = newNode(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !flags.isEmpty()) {
Node flagsNode = newStringNode(flags);
// Assume the flags are on the same line as the literal node.
flagsNode.setLineno(literalNode.getLineno());
node.addChildToBack(flagsNode);
}
return node;
}
@Override
Node processReturnStatement(ReturnStatement statementNode) {
Node node = newNode(Token.RETURN);
if (statementNode.getReturnValue() != null) {
node.addChildToBack(transform(statementNode.getReturnValue()));
}
return node;
}
@Override
Node processScope(Scope scopeNode) {
return processGeneric(scopeNode);
}
@Override
Node processStringLiteral(StringLiteral literalNode) {
Node n = newStringNode(literalNode.getValue());
return n;
}
@Override
Node processSwitchCase(SwitchCase caseNode) {
Node node;
if (caseNode.isDefault()) {
node = newNode(Token.DEFAULT);
} else {
AstNode expr = caseNode.getExpression();
node = newNode(Token.CASE, transform(expr));
}
Node block = newNode(Token.BLOCK);
block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
block.setLineno(caseNode.getLineno());
block.setCharno(position2charno(caseNode.getAbsolutePosition()));
if (caseNode.getStatements() != null) {
for (AstNode child : caseNode.getStatements()) {
block.addChildToBack(transform(child));
}
}
node.addChildToBack(block);
return node;
}
@Override
Node processSwitchStatement(SwitchStatement statementNode) {
Node node = newNode(Token.SWITCH,
transform(statementNode.getExpression()));
for (AstNode child : statementNode.getCases()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processThrowStatement(ThrowStatement statementNode) {
return newNode(Token.THROW,
transform(statementNode.getExpression()));
}
@Override
Node processTryStatement(TryStatement statementNode) {
Node node = newNode(Token.TRY,
transformBlock(statementNode.getTryBlock()));
Node block = newNode(Token.BLOCK);
boolean lineSet = false;
for (CatchClause cc : statementNode.getCatchClauses()) {
// Mark the enclosing block at the same line as the first catch
// clause.
if (lineSet == false) {
block.setLineno(cc.getLineno());
lineSet = true;
}
block.addChildToBack(transform(cc));
}
node.addChildToBack(block);
AstNode finallyBlock = statementNode.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>getFinallyBlock();
if (finallyBlock != null) {
node.addChildToBack(transformBlock(finallyBlock));
}
// If we didn't set the line on the catch clause, then
// we've got an empty catch clause. Set its line to be the same
// as the finally block (to match Old Rhino's behavior.)
if ((lineSet == false) && (finallyBlock != null)) {
block.setLineno(finallyBlock.getLineno());
}
return node;
}
@Override
Node processUnaryExpression(UnaryExpression exprNode) {
int type = transformTokenType(exprNode.getType());
Node operand = transform(exprNode.getOperand());
if (type == Token.NEG && operand.getType() == Token.NUMBER) {
operand.setDouble(-operand.getDouble());
return operand;
} else {
Node node = newNode(type, operand);
if (exprNode.isPostfix()) {
node.putBooleanProp(Node.INCRDECR_PROP, true);
}
return node;
}
}
@Override
Node processVariableDeclaration(VariableDeclaration declarationNode) {
Node node = newNode(Token.VAR);
for (VariableInitializer child : declarationNode.getVariables()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processVariableInitializer(VariableInitializer initializerNode) {
Node node = transform(initializerNode.getTarget());
if (initializerNode.getInitializer() != null) {
node.addChildToBack(transform(initializerNode.getInitializer()));
node.setLineno(node.getLineno());
}
return node;
}
@Override
Node processWhileLoop(WhileLoop loopNode) {
return newNode(
Token.WHILE,
transform(loopNode.getCondition()),
transformBlock(loopNode.getBody()));
}
@Override
Node processWithStatement(WithStatement statementNode) {
return newNode(
Token.WITH,
transform(statementNode.getExpression()),
transformBlock(statementNode.getStatement()));
}
@Override
Node processIllegalToken(AstNode node) {
errorReporter.error(
"Unsupported syntax: " +
com.google.javascript.jscomp.mozilla.rhino.Token.typeToName(
node.getType()),
sourceName,
node.getLineno(), "", 0);
return newNode(Token.EMPTY);
}
void reportDestructuringAssign(AstNode node) {
errorReporter.error(
"destructuring assignment forbidden",
sourceName,
node.getLineno(), "", 0);
}
void reportGetter(AstNode node) {
errorReporter.error(
"getters are not supported in Internet Explorer",
sourceName,
node.getLineno(), "", 0);
}
void reportSetter(AstNode node) {
errorReporter.error(
"setters are not supported in Internet Explorer",
sourceName,
node.getLineno(), "", 0);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
}
}
private static int transformTokenType(int token) {
switch (token) {
case com.google.javascript.jscomp.mozilla.rhino.Token.ERROR:
return Token.ERROR;
case com.google.javascript.jscomp.mozilla.rhino.Token.EOF:
return Token.EOF;
case com.google.javascript.jscomp.mozilla.rhino.Token.EOL:
return Token.EOL;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENTERWITH:
return Token.ENTERWITH;
case com.google.javascript.jscomp.mozilla.rhino.Token.LEAVEWITH:
return Token.LEAVEWITH;
case com.google.javascript.jscomp.mozilla.rhino.Token.RETURN:
return Token.RETURN;
case com.google.javascript.jscomp.mozilla.rhino.Token.GOTO:
return Token.GOTO;
case com.google.javascript.jscomp.mozilla.rhino.Token.IFEQ:
return Token.IFEQ;
case com.google.javascript.jscomp.mozilla.rhino.Token.IFNE:
return Token.IFNE;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETNAME:
return Token.SETNAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITOR:
return Token.BITOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITXOR:
return Token.BITXOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITAND:
return Token.BITAND;
case com.google.javascript.jscomp.mozilla.rhino.Token.EQ:
return Token.EQ;
case com.google.javascript.jscomp.mozilla.rhino.Token.NE:
return Token.NE;
case com.google.javascript.jscomp.mozilla.rhino.Token.LT:
return Token.LT;
case com.google.javascript.jscomp.mozilla.rhino.Token.LE:
return Token.LE;
case com.google.javascript.jscomp.mozilla.rhino.Token.GT:
return Token.GT;
case com.google.javascript.jscomp.mozilla.rhino.Token.GE:
return Token.GE;
case com.google.javascript.jscomp.mozilla.rhino.Token.LSH:
return Token.LSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.RSH:
return Token.RSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.URSH:
return Token.URSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.ADD:
return Token.ADD;
case com.google.javascript.jscomp.mozilla.rhino.Token.SUB:
return Token.SUB;
case
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> com.google.javascript.jscomp.mozilla.rhino.Token.MUL:
return Token.MUL;
case com.google.javascript.jscomp.mozilla.rhino.Token.DIV:
return Token.DIV;
case com.google.javascript.jscomp.mozilla.rhino.Token.MOD:
return Token.MOD;
case com.google.javascript.jscomp.mozilla.rhino.Token.NOT:
return Token.NOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITNOT:
return Token.BITNOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.POS:
return Token.POS;
case com.google.javascript.jscomp.mozilla.rhino.Token.NEG:
return Token.NEG;
case com.google.javascript.jscomp.mozilla.rhino.Token.NEW:
return Token.NEW;
case com.google.javascript.jscomp.mozilla.rhino.Token.DELPROP:
return Token.DELPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOF:
return Token.TYPEOF;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETPROP:
return Token.GETPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP:
return Token.SETPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETELEM:
return Token.GETELEM;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM:
return Token.SETELEM;
case com.google.javascript.jscomp.mozilla.rhino.Token.CALL:
return Token.CALL;
case com.google.javascript.jscomp.mozilla.rhino.Token.NAME:
return Token.NAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.NUMBER:
return Token.NUMBER;
case com.google.javascript.jscomp.mozilla.rhino.Token.STRING:
return Token.STRING;
case com.google.javascript.jscomp.mozilla.rhino.Token.NULL:
return Token.NULL;
case com.google.javascript.jscomp.mozilla.rhino.Token.THIS:
return Token.THIS;
case com.google.javascript.jscomp.mozilla.rhino.Token.FALSE:
return Token.FALSE;
case com.google.javascript.jscomp.mozilla.rhino.Token.TRUE:
return Token.TRUE;
case com.google.javascript.jscomp.mozilla.rhino.Token.SHEQ:
return Token.SHEQ;
case com.google.javascript.jscomp.mozilla.rhino.Token.SHNE:
return Token.SHNE;
case com.google.javascript.jscomp.mozilla.rhino.Token.REGEXP:
return Token.REGEXP
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
case com.google.javascript.jscomp.mozilla.rhino.Token.BINDNAME:
return Token.BINDNAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.THROW:
return Token.THROW;
case com.google.javascript.jscomp.mozilla.rhino.Token.RETHROW:
return Token.RETHROW;
case com.google.javascript.jscomp.mozilla.rhino.Token.IN:
return Token.IN;
case com.google.javascript.jscomp.mozilla.rhino.Token.INSTANCEOF:
return Token.INSTANCEOF;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_LOAD:
return Token.LOCAL_LOAD;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETVAR:
return Token.GETVAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETVAR:
return Token.SETVAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.CATCH_SCOPE:
return Token.CATCH_SCOPE;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_INIT_KEYS:
return Token.ENUM_INIT_KEYS;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_INIT_VALUES:
return Token.ENUM_INIT_VALUES;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_NEXT:
return Token.ENUM_NEXT;
case com.google.javascript.jscomp.mozilla.rhino.Token.ENUM_ID:
return Token.ENUM_ID;
case com.google.javascript.jscomp.mozilla.rhino.Token.THISFN:
return Token.THISFN;
case com.google.javascript.jscomp.mozilla.rhino.Token.RETURN_RESULT:
return Token.RETURN_RESULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.ARRAYLIT:
return Token.ARRAYLIT;
case com.google.javascript.jscomp.mozilla.rhino.Token.OBJECTLIT:
return Token.OBJECTLIT;
case com.google.javascript.jscomp.mozilla.rhino.Token.GET_REF:
return Token.GET_REF;
case com.google.javascript.jscomp.mozilla.rhino.Token.SET_REF:
return Token.SET_REF;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEL_REF:
return Token.DEL_REF;
case com.google.javascript.jscomp.mozilla.rhino.Token.REF_CALL:
return Token.REF_CALL;
case com.google.javascript.jscomp.mozilla.rhino.Token.REF_SPECIAL:
return Token.REF_SPECIAL;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEFAULTNAMESPACE:
return Token
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.DEFAULTNAMESPACE;
case com.google.javascript.jscomp.mozilla.rhino.Token.ESCXMLTEXT:
return Token.ESCXMLTEXT;
case com.google.javascript.jscomp.mozilla.rhino.Token.ESCXMLATTR:
return Token.ESCXMLATTR;
case com.google.javascript.jscomp.mozilla.rhino.Token.REF_MEMBER:
return Token.REF_MEMBER;
case com.google.javascript.jscomp.mozilla.rhino.Token.REF_NS_MEMBER:
return Token.REF_NS_MEMBER;
case com.google.javascript.jscomp.mozilla.rhino.Token.REF_NAME:
return Token.REF_NAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.REF_NS_NAME:
return Token.REF_NS_NAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.TRY:
return Token.TRY;
case com.google.javascript.jscomp.mozilla.rhino.Token.SEMI:
return Token.SEMI;
case com.google.javascript.jscomp.mozilla.rhino.Token.LB:
return Token.LB;
case com.google.javascript.jscomp.mozilla.rhino.Token.RB:
return Token.RB;
case com.google.javascript.jscomp.mozilla.rhino.Token.LC:
return Token.LC;
case com.google.javascript.jscomp.mozilla.rhino.Token.RC:
return Token.RC;
case com.google.javascript.jscomp.mozilla.rhino.Token.LP:
return Token.LP;
case com.google.javascript.jscomp.mozilla.rhino.Token.RP:
return Token.RP;
case com.google.javascript.jscomp.mozilla.rhino.Token.COMMA:
return Token.COMMA;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN:
return Token.ASSIGN;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITOR:
return Token.ASSIGN_BITOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITXOR:
return Token.ASSIGN_BITXOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITAND:
return Token.ASSIGN_BITAND;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_LSH:
return Token.ASSIGN_LSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_RSH:
return Token.ASSIGN_RSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_URSH:
return Token.ASSIGN_URSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_ADD:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> return Token.ASSIGN_ADD;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_SUB:
return Token.ASSIGN_SUB;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MUL:
return Token.ASSIGN_MUL;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV:
return Token.ASSIGN_DIV;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MOD:
return Token.ASSIGN_MOD;
case com.google.javascript.jscomp.mozilla.rhino.Token.HOOK:
return Token.HOOK;
case com.google.javascript.jscomp.mozilla.rhino.Token.COLON:
return Token.COLON;
case com.google.javascript.jscomp.mozilla.rhino.Token.OR:
return Token.OR;
case com.google.javascript.jscomp.mozilla.rhino.Token.AND:
return Token.AND;
case com.google.javascript.jscomp.mozilla.rhino.Token.INC:
return Token.INC;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEC:
return Token.DEC;
case com.google.javascript.jscomp.mozilla.rhino.Token.DOT:
return Token.DOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.FUNCTION:
return Token.FUNCTION;
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPORT:
return Token.EXPORT;
case com.google.javascript.jscomp.mozilla.rhino.Token.IMPORT:
return Token.IMPORT;
case com.google.javascript.jscomp.mozilla.rhino.Token.IF:
return Token.IF;
case com.google.javascript.jscomp.mozilla.rhino.Token.ELSE:
return Token.ELSE;
case com.google.javascript.jscomp.mozilla.rhino.Token.SWITCH:
return Token.SWITCH;
case com.google.javascript.jscomp.mozilla.rhino.Token.CASE:
return Token.CASE;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEFAULT:
return Token.DEFAULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.WHILE:
return Token.WHILE;
case com.google.javascript.jscomp.mozilla.rhino.Token.DO:
return Token.DO;
case com.google.javascript.jscomp.mozilla.rhino.Token.FOR:
return Token.FOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.BREAK:
return Token.BREAK;
case com.google.javascript.jscomp.mozilla.rhino.Token.CONTINUE:
return Token.CONTINUE;
case com.google.javascript.jscomp.mozilla.rhino.Token.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>VAR:
return Token.VAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.WITH:
return Token.WITH;
case com.google.javascript.jscomp.mozilla.rhino.Token.CATCH:
return Token.CATCH;
case com.google.javascript.jscomp.mozilla.rhino.Token.FINALLY:
return Token.FINALLY;
case com.google.javascript.jscomp.mozilla.rhino.Token.VOID:
return Token.VOID;
case com.google.javascript.jscomp.mozilla.rhino.Token.RESERVED:
return Token.RESERVED;
case com.google.javascript.jscomp.mozilla.rhino.Token.EMPTY:
return Token.EMPTY;
case com.google.javascript.jscomp.mozilla.rhino.Token.BLOCK:
return Token.BLOCK;
case com.google.javascript.jscomp.mozilla.rhino.Token.LABEL:
return Token.LABEL;
case com.google.javascript.jscomp.mozilla.rhino.Token.TARGET:
return Token.TARGET;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOOP:
return Token.LOOP;
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_VOID:
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_RESULT:
return Token.EXPR_RESULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.JSR:
return Token.JSR;
case com.google.javascript.jscomp.mozilla.rhino.Token.SCRIPT:
return Token.SCRIPT;
case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOFNAME:
return Token.TYPEOFNAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.USE_STACK:
return Token.USE_STACK;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP_OP:
return Token.SETPROP_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM_OP:
return Token.SETELEM_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_BLOCK:
return Token.LOCAL_BLOCK;
case com.google.javascript.jscomp.mozilla.rhino.Token.SET_REF_OP:
return Token.SET_REF_OP;
case com.google.javascript.jscomp.mozilla.rhino.Token.DOTDOT:
return Token.DOTDOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.COLONCOLON:
return Token.COLONCOLON;
case com.google.javascript.jscomp.mozilla.rhino.Token.XML:
return Token.XML;
case com.google.javascript.jscomp.mozilla.rhino.Token
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.DOTQUERY:
return Token.DOTQUERY;
case com.google.javascript.jscomp.mozilla.rhino.Token.XMLATTR:
return Token.XMLATTR;
case com.google.javascript.jscomp.mozilla.rhino.Token.XMLEND:
return Token.XMLEND;
case com.google.javascript.jscomp.mozilla.rhino.Token.TO_OBJECT:
return Token.TO_OBJECT;
case com.google.javascript.jscomp.mozilla.rhino.Token.TO_DOUBLE:
return Token.TO_DOUBLE;
case com.google.javascript.jscomp.mozilla.rhino.Token.GET:
return Token.GET;
case com.google.javascript.jscomp.mozilla.rhino.Token.SET:
return Token.SET;
case com.google.javascript.jscomp.mozilla.rhino.Token.CONST:
return Token.CONST;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETCONST:
return Token.SETCONST;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEBUGGER:
return Token.DEBUGGER;
}
// Token without name
throw new IllegalStateException(String.valueOf(token));
}
// Simple helper to create nodes and set the initial node properties.
private Node newNode(int type) {
return new Node(type).clonePropsFrom(templateNode);
}
private Node newNode(int type, Node child1) {
return new Node(type, child1).clonePropsFrom(templateNode);
}
private Node newNode(int type, Node child1, Node child2) {
return new Node(type, child1, child2).clonePropsFrom(templateNode);
}
private Node newNode(int type, Node child1, Node child2, Node child3) {
return new Node(type, child1, child2, child3).clonePropsFrom(templateNode);
}
private Node newStringNode(String value) {
return Node.newString(value).clonePropsFrom(templateNode);
}
private Node newStringNode(int type, String value) {
return Node.newString(type, value).clonePropsFrom(templateNode);
}
private Node newNumberNode(Double value) {
return Node.newNumber(value).clonePropsFrom(templateNode);
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2007 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.javascript.jscomp.SourceExcerptProvider.SourceExcerpt.LINE;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.SourceExcerptProvider.ExcerptFormatter;
import com.google.javascript.jscomp.SourceExcerptProvider.SourceExcerpt;
/**
* Lightweight message formatter. The format of messages this formatter
* produces is very compact and to the point.
*
*/
public class LightweightMessageFormatter extends AbstractMessageFormatter {
private SourceExcerpt excerpt;
private static final ExcerptFormatter excerptFormatter =
new LineNumberingFormatter();
/**
* A constructor for when the client doesn't care about source information.
*/
private LightweightMessageFormatter() {
super(null);
this.excerpt = LINE;
}
public LightweightMessageFormatter(SourceExcerptProvider source) {
this(source, LINE);
}
public LightweightMessageFormatter(SourceExcerptProvider source,
SourceExcerpt excerpt) {
super(source);
Preconditions.checkNotNull(source);
this.excerpt = excerpt;
}
static LightweightMessageFormatter withoutSource() {
return new LightweightMessageFormatter();
}
public String formatError(JSError error) {
return format(error, false);
}
public String formatWarning(JSError warning) {
return format(warning, true);
}
private String format(JSError error, boolean warning) {
// extract source excerpt
SourceExcerptProvider source = getSource();
String sourceExcerpt = source == null ? null :
excerpt.get(
source, error.sourceName, error.lineNumber, excerptFormatter);
// formatting the message
StringBuilder b = new StringBuilder();
if (error.sourceName != null) {
b.append(error.sourceName);
if (error.lineNumber > 0) {
b.append(':');
b.append(error.lineNumber);
}
b.append(": ");
}
b.append(getLevelName(warning ? CheckLevel.WARNING : CheckLevel.ERROR));
b.append(" - ");
b.append(error.description);
b.append('\n');
if (sourceEx
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> input, Set<INPUT> subGraph) {
for (String symbol : input.getRequires()) {
INPUT candidate = provideMap.get(symbol);
if (subGraph.contains(candidate)) {
return candidate;
}
}
throw new IllegalStateException("no require found in subgraph");
}
/**
* @param cycle A cycle in reverse-dependency order.
*/
private String cycleToString(List<INPUT> cycle) {
List<String> symbols = Lists.newArrayList();
for (int i = cycle.size() - 1; i >= 0; i--) {
symbols.add(cycle.get(i).getProvides().iterator().next());
}
symbols.add(symbols.get(0));
return Joiner.on(" -> ").join(symbols);
}
public List<INPUT> getSortedList() {
return Collections.<INPUT>unmodifiableList(sortedList);
}
/**
* Gets all the dependencies of the given roots. The inputs must be returned
* in a stable order. In other words, if A comes before B, and A does not
* transitively depend on B, then A must also come before B in the returned
* list.
*/
public List<INPUT> getSortedDependenciesOf(List<INPUT> roots) {
Preconditions.checkArgument(inputs.containsAll(roots));
Set<INPUT> included = Sets.newHashSet();
Deque<INPUT> worklist = new ArrayDeque<INPUT>(roots);
while (!worklist.isEmpty()) {
INPUT current = worklist.pop();
if (included.add(current)) {
for (String req : current.getRequires()) {
INPUT dep = provideMap.get(req);
if (dep != null) {
worklist.add(dep);
}
}
}
}
ImmutableList.Builder<INPUT> builder = ImmutableList.builder();
for (INPUT current : sortedList) {
if (included.contains(current)) {
builder.add(current);
}
}
return builder.build();
}
public List<INPUT> getInputsWithoutProvides() {
return Collections.<INPUT>unmodifiableList(noProvides);
}
private static <T> List<T> topologicalStableSort(
List<T> items, Multimap<T, T> deps) {
final Map<T, Integer> originalIndex = Maps.newHashMap();
for (int i = 0; i < items.size(); i++) {
originalIndex.put(items.get(i), i);
}
PriorityQueue<T> inDegreeZero = new PriorityQueue<T>(items.size(),
new Comparator<T>() {
@Override
public int compare(T a, T b) {
return originalIndex.get(a).intValue() -
originalIndex.get(b).intValue();
}
});
List<T> result = Lists.newArrayList();
Multiset<T> inDegree = HashMultiset.create();
Multimap<T, T> reverseDeps = ArrayListMultimap.create();
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>();
if (grandparent.getType() == Token.NAME
&& grandparent.getString() == v.name) {
continue;
}
// Only generate warnings if the scopes do not match in order
// to deal with possible forward declarations and recursion
if (reference.getScope() == v.scope) {
compiler.report(
JSError.make(reference.getSourceName(),
reference.getNameNode(),
checkLevel,
UNDECLARED_REFERENCE, v.name));
}
}
if (isDeclaration) {
blocksWithDeclarations.add(basicBlock);
isDeclaredInScope = true;
}
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {
return "{...}";
}
}
void setPrettyPrint(boolean prettyPrint) {
this.prettyPrint = prettyPrint;
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
public ObjectType getImplicitPrototype() {
return implicitPrototypeFallback;
}
/**
* This should only be reset on the FunctionPrototypeType, only to fix an
* incorrectly established prototype chain due to the user having a mismatch
* in super class declaration, and only before properties on that type are
* processed.
*/
final void setImplicitPrototype(ObjectType implicitPrototype) {
checkState(!hasCachedValues());
this.implicitPrototypeFallback = implicitPrototype;
}
@Override
public String getReferenceName() {
if (className != null) {
return className;
} else {
return null;
}
}
@Override
public boolean hasReferenceName() {
return className != null;
}
@Override
public boolean isSubtype(JSType that) {
if (JSType.isSubtype(this, that)) {
return true;
}
// Union types
if (that instanceof UnionType) {
// The static {@code JSType.isSubtype} check already decomposed
// union types, so we don't need to check those again.
return false;
}
// record types
if (that instanceof RecordType) {
return RecordType.isSubtype(this, (RecordType) that);
}
// Interfaces
// Find all the interfaces implemented by this class and compare each one
// to the interface instance.
ObjectType thatObj = that.toObjectType();
ObjectType thatCtor = thatObj == null ? null : thatObj.getConstructor();
if (thatCtor != null && thatCtor.isInterface()) {
Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces();
for (ObjectType thisInterface : thisInterfaces) {
if (thisInterface.isSubtype(that)) {
return true;
}
}
}
// other prototype based objects
if (that != null) {
if (isUnknownType() || implicitPrototypeChainIsUnknown()) {
// If unsure, say 'yes', to avoid spurious warnings.
// TODO(user): resolve the prototype chain completely in all cases,
// to avoid guessing.
return true;
}
return this.isImplicitPrototype(thatObj);
}
return false;
}
private boolean implicitPrototypeChainIsUnknown() {
ObjectType p = getImplicitPrototype();
while (p != null) {
if (p.isUnknownType()) {
return true;
}
p = p.getImplicitPrototype();
}
return false;
}
private static final class Property implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Property's type.
*/
private JSType type;
/**
* Whether the property's type is inferred.
*/
private final boolean inferred;
/**
* Whether the property is defined in the externs.
*/
private final boolean inExterns;
/**
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Type.error("JSC_OPTIMIZE_LOOP_ERROR",
"Exceeded max number of code motion iterations: {0}");
private static final long COMPILER_STACK_SIZE = 1048576L;
/**
* Logger for the whole com.google.javascript.jscomp domain -
* setting configuration for this logger affects all loggers
* in other classes within the compiler.
*/
private static final Logger logger =
Logger.getLogger("com.google.javascript.jscomp");
private final PrintStream outStream;
/**
* Creates a Compiler that reports errors and warnings to its logger.
*/
public Compiler() {
this((PrintStream) null);
}
/**
* Creates n Compiler that reports errors and warnings to an output
* stream.
*/
public Compiler(PrintStream stream) {
addChangeHandler(recentChange);
outStream = stream;
}
/**
* Creates a Compiler that uses a custom error manager.
*/
public Compiler(ErrorManager errorManager) {
this();
setErrorManager(errorManager);
}
/**
* Sets the error manager.
*
* @param errorManager the error manager, it cannot be {@code null}
*/
public void setErrorManager(ErrorManager errorManager) {
Preconditions.checkNotNull(
errorManager, "the error manager cannot be null");
this.errorManager = errorManager;
}
/**
* Creates a message formatter instance corresponding to the value of
* {@link CompilerOptions}.
*/
private MessageFormatter createMessageFormatter() {
boolean colorize = options.shouldColorizeErrorOutput();
return options.errorFormat.toFormatter(this, colorize);
}
/**
* Initialize the compiler options. Only necessary if you're not doing
* a normal compile() job.
*/
public void initOptions(CompilerOptions options) {
this.options = options;
if (errorManager == null) {
if (outStream == null) {
setErrorManager(
new LoggerErrorManager(createMessageFormatter(), logger));
} else {
PrintStreamErrorManager printer =
new PrintStreamErrorManager(createMessageFormatter(), outStream);
printer.setSummaryDetailLevel(options.summaryDetailLevel);
setErrorManager(printer);
}
}
// Initialize the warnings guard.
List<WarningsGuard> guards = Lists.newArrayList();
guards.add(
new SuppressDocWarningsGuard(
getDiagnosticGroups().getRegisteredGroups()));
WarningsGuard warningsGuard = options.getWarningsGuard();
if (warningsGuard != null) {
guards.add(options.getWarningsGuard());
}
// All passes must run the variable check. This synthesizes
// variables later so that the compiler doesn't crash. It also
// checks the externs file for validity. If you don't want to warn
// about missing variable declarations, we shut that specific
// error off.
if (!options.checkSymbols &&
(warningsGuard == null || !warningsGuard.disables(
DiagnosticGroups.CHECK_VARIABLES))) {
gu
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>}");
/**
* Creates a map to make looking up an input by name fast. Also checks for
* duplicate inputs.
*/
void initInputsByNameMap() {
inputsByName = new HashMap<String, CompilerInput>();
for (CompilerInput input : externs) {
String name = input.getName();
if (!inputsByName.containsKey(name)) {
inputsByName.put(name, input);
} else {
report(JSError.make(DUPLICATE_EXTERN_INPUT, name));
}
}
for (CompilerInput input : inputs) {
String name = input.getName();
if (!inputsByName.containsKey(name)) {
inputsByName.put(name, input);
} else {
report(JSError.make(DUPLICATE_INPUT, name));
}
}
}
public Result compile(
JSSourceFile extern, JSSourceFile input, CompilerOptions options) {
return compile(extern, new JSSourceFile[] { input }, options);
}
public Result compile(
JSSourceFile extern, JSSourceFile[] input, CompilerOptions options) {
return compile(new JSSourceFile[] { extern }, input, options);
}
public Result compile(
JSSourceFile extern, JSModule[] modules, CompilerOptions options) {
return compile(new JSSourceFile[] { extern }, modules, options);
}
/**
* Compiles a list of inputs.
*/
public Result compile(JSSourceFile[] externs,
JSSourceFile[] inputs,
CompilerOptions options) {
return compile(Lists.<JSSourceFile>newArrayList(externs),
Lists.<JSSourceFile>newArrayList(inputs),
options);
}
/**
* Compiles a list of inputs.
*/
public Result compile(List<JSSourceFile> externs,
List<JSSourceFile> inputs, CompilerOptions options) {
// The compile method should only be called once.
Preconditions.checkState(jsRoot == null);
try {
init(externs, inputs, options);
if (hasErrors()) {
return getResult();
}
return compile();
} finally {
Tracer t = newTracer("generateReport");
errorManager.generateReport();
stopTracer(t, "generateReport");
}
}
/**
* Compiles a list of modules.
*/
public Result compile(JSSourceFile[] externs,
JSModule[] modules,
CompilerOptions options) {
return compileModules(Lists.<JSSourceFile>newArrayList(externs),
Lists.<JSModule>newArrayList(modules),
options);
}
/**
* Compiles a list of modules.
*/
public Result compileModules(List<JSSourceFile> externs,
List<JSModule> modules, CompilerOptions options) {
// The compile method should only be called once.
Preconditions.checkState(jsRoot == null);
try {
initModules(externs, modules, options);
if (hasErrors()) {
return getResult();
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ExternExportsEnabled()
|| options.externExportsPath != null) {
externExports();
}
// IDE-mode is defined to stop here, before the heavy rewriting begins.
if (!options.ideMode) {
optimize();
}
}
if (options.recordFunctionInformation) {
recordFunctionInformation();
}
if (options.devMode == DevMode.START_AND_END) {
runSanityCheck();
}
}
public void parse() {
parseInputs();
}
PassConfig getPassConfig() {
if (passes == null) {
passes = createPassConfigInternal();
}
return passes;
}
/**
* Create the passes object. Clients should use setPassConfig instead of
* overriding this.
*/
PassConfig createPassConfigInternal() {
return new DefaultPassConfig(options);
}
/**
* @param passes The PassConfig to use with this Compiler.
* @throws NullPointerException if passes is null
* @throws IllegalStateException if this.passes has already been assigned
*/
public void setPassConfig(PassConfig passes) {
// Important to check for null because if setPassConfig(null) is
// called before this.passes is set, getPassConfig() will create a
// new PassConfig object and use that, which is probably not what
// the client wanted since he or she probably meant to use their
// own PassConfig object.
Preconditions.checkNotNull(passes);
if (this.passes != null) {
throw new IllegalStateException("this.passes has already been assigned");
}
this.passes = passes;
}
/**
* Carry out any special checks or procedures that need to be done before
* proceeding with rest of the compilation process.
*
* @return true, to continue with compilation
*/
boolean precheck() {
return true;
}
public void check() {
runCustomPasses(CustomPassExecutionTime.BEFORE_CHECKS);
PhaseOptimizer phaseOptimizer = new PhaseOptimizer(this, tracker);
if (options.devMode == DevMode.EVERY_PASS) {
phaseOptimizer.setSanityCheck(sanityCheck);
}
phaseOptimizer.consume(getPassConfig().getChecks());
phaseOptimizer.process(externsRoot, jsRoot);
if (hasErrors()) {
return;
}
// TODO(nicksantos): clean this up. The flow here is too hard to follow.
if (options.nameAnonymousFunctionsOnly) {
return;
}
if (options.removeTryCatchFinally) {
removeTryCatchFinally();
}
if (!options.stripTypes.isEmpty() ||
!options.stripNameSuffixes.isEmpty() ||
!options.stripTypePrefixes.isEmpty() ||
!options.stripNamePrefixes.isEmpty()) {
stripCode(options.stripTypes, options.stripNameSuffixes,
options.stripTypePrefixes, options.stripNamePrefixes);
}
runCustomPasses(CustomPassExecutionTime.BEFORE_OPTIMIZATIONS);
}
private void externExports
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>() {
logger.info("Creating extern file for exports");
startPass("externExports");
ExternExportsPass pass = new ExternExportsPass(this);
process(pass);
externExports = pass.getGeneratedExterns();
endPass();
}
void process(CompilerPass p) {
p.process(externsRoot, jsRoot);
}
private final PassFactory sanityCheck =
new PassFactory("sanityCheck", false) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new SanityCheck(compiler);
}
};
private void maybeSanityCheck() {
if (options.devMode == DevMode.EVERY_PASS) {
runSanityCheck();
}
}
private void runSanityCheck() {
sanityCheck.create(this).process(externsRoot, jsRoot);
}
/**
* Removes try/catch/finally statements for easier debugging.
*/
void removeTryCatchFinally() {
logger.info("Remove try/catch/finally");
startPass("removeTryCatchFinally");
RemoveTryCatch r = new RemoveTryCatch(this);
process(r);
endPass();
}
/**
* Strips code for smaller compiled code. This is useful for removing debug
* statements to prevent leaking them publicly.
*/
void stripCode(Set<String> stripTypes, Set<String> stripNameSuffixes,
Set<String> stripTypePrefixes, Set<String> stripNamePrefixes) {
logger.info("Strip code");
startPass("stripCode");
StripCode r = new StripCode(this, stripTypes, stripNameSuffixes,
stripTypePrefixes, stripNamePrefixes);
process(r);
endPass();
}
/**
* Runs custom passes that are designated to run at a particular time.
*/
private void runCustomPasses(CustomPassExecutionTime executionTime) {
if (options.customPasses != null) {
Tracer t = newTracer("runCustomPasses");
try {
for (CompilerPass p : options.customPasses.get(executionTime)) {
process(p);
}
} finally {
stopTracer(t, "runCustomPasses");
}
}
}
private Tracer currentTracer = null;
private String currentPassName = null;
/**
* Marks the beginning of a pass.
*/
void startPass(String passName) {
Preconditions.checkState(currentTracer == null);
currentPassName = passName;
currentTracer = newTracer(passName);
}
/**
* Marks the end of a pass.
*/
void endPass() {
Preconditions.checkState(currentTracer != null,
"Tracer should not be null at the end of a pass.");
stopTracer(currentTracer, currentPassName);
String passToCheck = currentPassName;
currentPassName = null;
currentTracer = null;
maybeSanityCheck();
}
/**
* Returns a new tracer for the given pass
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> } else {
return n1.checkTreeEqualsSilent(n2);
}
}
//------------------------------------------------------------------------
// Inputs
//------------------------------------------------------------------------
// TODO(nicksantos): Decide which parts of these belong in an AbstractCompiler
// interface, and which ones should always be injected.
@Override
public CompilerInput getInput(String name) {
return inputsByName.get(name);
}
@Override
public CompilerInput newExternInput(String name) {
if (inputsByName.containsKey(name)) {
throw new IllegalArgumentException("Conflicting externs name: " + name);
}
SourceAst ast = new SyntheticAst(name);
CompilerInput input = new CompilerInput(ast, name, true);
inputsByName.put(name, input);
externsRoot.addChildToFront(ast.getAstRoot(this));
return input;
}
/** Add a source input dynamically. Intended for incremental compilation. */
void addIncrementalSourceAst(JsAst ast) {
String sourceName = ast.getSourceFile().getName();
Preconditions.checkState(
getInput(sourceName) == null,
"Duplicate input of name " + sourceName);
inputsByName.put(sourceName, new CompilerInput(ast));
}
/**
* Replace a source input dynamically. Intended for incremental
* re-compilation.
*
* If the new source input doesn't parse, then keep the old input
* in the AST and return false.
*
* @return Whether the new AST was attached successfully.
*/
boolean replaceIncrementalSourceAst(JsAst ast) {
String sourceName = ast.getSourceFile().getName();
CompilerInput oldInput =
Preconditions.checkNotNull(
getInput(sourceName),
"No input to replace: " + sourceName);
Node newRoot = ast.getAstRoot(this);
if (newRoot == null) {
return false;
}
Node oldRoot = oldInput.getAstRoot(this);
if (oldRoot != null) {
oldRoot.getParent().replaceChild(oldRoot, newRoot);
} else {
getRoot().getLastChild().addChildToBack(newRoot);
}
inputsByName.put(sourceName, new CompilerInput(ast));
return true;
}
@Override
JSModuleGraph getModuleGraph() {
return moduleGraph;
}
@Override
public JSTypeRegistry getTypeRegistry() {
if (typeRegistry == null) {
typeRegistry = new JSTypeRegistry(oldErrorReporter, options.looseTypes);
}
return typeRegistry;
}
@Override
ScopeCreator getScopeCreator() {
return getPassConfig().getScopeCreator();
}
@Override
public Scope getTopScope() {
return getPassConfig().getTopScope();
}
@Override
public ReverseAbstractInterpreter getReverseAbstractInterpreter() {
if (abstractInterpreter == null) {
ChainableReverseAbstractInterpreter interpreter =
new SemanticReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry());
if (options.closurePass) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> interpreter = new ClosureReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry())
.append(interpreter).getFirst();
}
abstractInterpreter = interpreter;
}
return abstractInterpreter;
}
@Override
TypeValidator getTypeValidator() {
if (typeValidator == null) {
typeValidator = new TypeValidator(this);
}
return typeValidator;
}
//------------------------------------------------------------------------
// Parsing
//------------------------------------------------------------------------
/**
* Parses the externs and main inputs.
*
* @return A synthetic root node whose two children are the externs root
* and the main root
*/
Node parseInputs() {
boolean devMode = options.devMode != DevMode.OFF;
// If old roots exist (we are parsing a second time), detach each of the
// individual file parse trees.
if (externsRoot != null) {
externsRoot.detachChildren();
}
if (jsRoot != null) {
jsRoot.detachChildren();
}
// Parse main js sources.
jsRoot = new Node(Token.BLOCK);
jsRoot.setIsSyntheticBlock(true);
if (options.tracer.isOn()) {
tracker = new PerformanceTracker(jsRoot,
options.tracer == TracerMode.ALL);
addChangeHandler(tracker.getCodeChangeHandler());
}
Tracer tracer = newTracer("parseInputs");
try {
// Parse externs sources.
externsRoot = new Node(Token.BLOCK);
externsRoot.setIsSyntheticBlock(true);
for (CompilerInput input : externs) {
Node n = input.getAstRoot(this);
if (hasErrors()) {
return null;
}
externsRoot.addChildToBack(n);
}
// Check if the sources need to be re-ordered.
if (options.manageClosureDependencies) {
for (CompilerInput input : inputs) {
input.setCompiler(this);
// Forward-declare all the provided types, so that they
// are not flagged even if they are dropped from the process.
for (String provide : input.getProvides()) {
getTypeRegistry().forwardDeclareType(provide);
}
}
try {
inputs =
(moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph)
.manageDependencies(
options.manageClosureDependenciesEntryPoints, inputs);
} catch (CircularDependencyException e) {
report(JSError.make(
JSModule.CIRCULAR_DEPENDENCY_ERROR, e.getMessage()));
return null;
} catch (MissingProvideException e) {
report(JSError.make(
MISSING_ENTRY_ERROR, e.getMessage()));
return null;
}
}
// Check if inputs need to be rebuilt from modules.
boolean staleInputs = false;
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
if (hasErrors()) {
return null;
}
// Inputs can have a null AST during initial parse.
if (n == null) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> continue;
}
if (n.getJSDocInfo() != null) {
JSDocInfo info = n.getJSDocInfo();
if (info.isExterns()) {
// If the input file is explicitly marked as an externs file, then
// assume the programmer made a mistake and throw it into
// the externs pile anyways.
externsRoot.addChildToBack(n);
input.setIsExtern(true);
input.getModule().remove(input);
externs.add(input);
staleInputs = true;
} else if (info.isNoCompile()) {
input.getModule().remove(input);
staleInputs = true;
}
}
}
if (staleInputs) {
fillEmptyModules(modules);
rebuildInputsFromModules();
}
// Build the AST.
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
if (n == null) {
continue;
}
if (devMode) {
runSanityCheck();
if (hasErrors()) {
return null;
}
}
if (options.sourceMapOutputPath != null ||
options.nameReferenceReportPath != null) {
// Annotate the nodes in the tree with information from the
// input file. This information is used to construct the SourceMap.
SourceInformationAnnotator sia =
new SourceInformationAnnotator(
input.getName(), options.devMode != DevMode.OFF);
NodeTraversal.traverse(this, n, sia);
}
jsRoot.addChildToBack(n);
}
externAndJsRoot = new Node(Token.BLOCK, externsRoot, jsRoot);
externAndJsRoot.setIsSyntheticBlock(true);
return externAndJsRoot;
} finally {
stopTracer(tracer, "parseInputs");
}
}
public Node parse(JSSourceFile file) {
initCompilerOptionsIfTesting();
addToDebugLog("Parsing: " + file.getName());
return new JsAst(file).getAstRoot(this);
}
@Override
Node parseSyntheticCode(String js) {
CompilerInput input = new CompilerInput(
JSSourceFile.fromCode(" [synthetic] ", js));
inputsByName.put(input.getName(), input);
return input.getAstRoot(this);
}
void initCompilerOptionsIfTesting() {
if (options == null) {
// initialization for tests that don't initialize the compiler
// by the normal mechanisms.
initOptions(new CompilerOptions());
}
}
@Override
Node parseSyntheticCode(String fileName, String js) {
initCompilerOptionsIfTesting();
return parse(JSSourceFile.fromCode(fileName, js));
}
@Override
Node parseTestCode(String js) {
initCompilerOptionsIfTesting();
CompilerInput input = new CompilerInput(
JSSourceFile.fromCode(" [testcode] ", js));
if (inputsByName == null)
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Inputs = inputs.size();
if (numInputs == 0) {
return new String[0];
}
String[] sources = new String[numInputs];
CodeBuilder cb = new CodeBuilder();
for (int i = 0; i < numInputs; i++) {
Node scriptNode = inputs.get(i).getAstRoot(Compiler.this);
if (scriptNode == null) {
throw new IllegalArgumentException(
"Bad module input: " + inputs.get(i).getName());
}
cb.reset();
toSource(cb, i, scriptNode);
sources[i] = cb.toString();
}
return sources;
}
});
}
/**
* Writes out js code from a root node. If printing input delimiters, this
* method will attach a comment to the start of the text indicating which
* input the output derived from. If there were any preserve annotations
* within the root's source, they will also be printed in a block comment
* at the beginning of the output.
*/
public void toSource(final CodeBuilder cb,
final int inputSeqNum,
final Node root) {
runInCompilerThread(new Callable<Void>() {
public Void call() throws Exception {
if (options.printInputDelimiter) {
if ((cb.getLength() > 0) && !cb.endsWith("\n")) {
cb.append("\n"); // Make sure that the label starts on a new line
}
Preconditions.checkState(root.getType() == Token.SCRIPT);
String delimiter = options.inputDelimiter;
String sourceName = (String)root.getProp(Node.SOURCENAME_PROP);
Preconditions.checkState(sourceName != null);
Preconditions.checkState(!sourceName.isEmpty());
delimiter = delimiter.replaceAll("%name%", sourceName)
.replaceAll("%num%", String.valueOf(inputSeqNum));
cb.append(delimiter)
.append("\n");
}
if (root.getJSDocInfo() != null &&
root.getJSDocInfo().getLicense() != null) {
cb.append("/*\n")
.append(root.getJSDocInfo().getLicense())
.append("*/\n");
}
// If there is a valid source map, then indicate to it that the current
// root node's mappings are offset by the given string builder buffer.
if (options.sourceMapOutputPath != null) {
sourceMap.setStartingPosition(
cb.getLineIndex(), cb.getColumnIndex());
}
String code = toSource(root, sourceMap);
if (!code.isEmpty()) {
cb.append(code);
if (!code.endsWith(";")) {
cb.append(";");
}
}
return null;
}
});
}
/**
* Generates JavaScript source code for an AST, doesn't generate source
* map info.
*/
@Override
String toSource(Node n) {
initCompilerOptionsIfTesting();
return toSource(n, null);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
@Override
public void reportCodeChange() {
for (CodeChangeHandler handler : codeChangeHandlers) {
handler.reportChange();
}
}
@Override
public CodingConvention getCodingConvention() {
CodingConvention convention = options.getCodingConvention();
convention = convention != null ? convention : defaultCodingConvention;
return convention;
}
@Override
public boolean isIdeMode() {
return options.ideMode;
}
@Override
public boolean acceptEcmaScript5() {
return options.languageIn == LanguageMode.ECMASCRIPT5;
}
@Override
Config getParserConfig() {
if (parserConfig == null) {
parserConfig = ParserRunner.createConfig(
isIdeMode(), acceptEcmaScript5());
}
return parserConfig;
}
@Override
public boolean isTypeCheckingEnabled() {
return options.checkTypes;
}
//------------------------------------------------------------------------
// Error reporting
//------------------------------------------------------------------------
/**
* The warning classes that are available from the command-line, and
* are suppressable by the {@code @suppress} annotation.
*/
protected DiagnosticGroups getDiagnosticGroups() {
return new DiagnosticGroups();
}
@Override
public void report(JSError error) {
CheckLevel level = error.level;
if (warningsGuard != null) {
CheckLevel newLevel = warningsGuard.level(error);
if (newLevel != null) {
level = newLevel;
}
}
if (level.isOn()) {
errorManager.report(level, error);
}
}
@Override
public CheckLevel getErrorLevel(JSError error) {
Preconditions.checkNotNull(options);
WarningsGuard guards = options.getWarningsGuard();
if (guards == null) {
return error.level;
} else {
return guards.level(error);
}
}
/**
* Report an internal error.
*/
@Override
void throwInternalError(String message, Exception cause) {
String finalMessage =
"INTERNAL COMPILER ERROR.\n" +
"Please report this problem.\n" + message;
RuntimeException e = new RuntimeException(finalMessage, cause);
if (cause != null) {
e.setStackTrace(cause.getStackTrace());
}
throw e;
}
/**
* Gets the number of errors.
*/
public int getErrorCount() {
return errorManager.getErrorCount();
}
/**
* Gets the number of warnings.
*/
public int getWarningCount() {
return errorManager.getWarningCount();
}
@Override
boolean hasHaltingErrors() {
return !isIdeMode() && getErrorCount() > 0;
}
/**
* Consults the {@link ErrorManager} to see if we've encountered errors
* that should halt compilation. <p>
*
* If {@link CompilerOptions#ideMode} is {@code true}, this function
* always returns {@code false} without consulting the error manager. The
* error manager will continue to
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2007 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Converts property accesses from quoted string syntax to dot syntax, where
* possible. Dot syntax is more compact and avoids an object allocation in
* IE 6.
*
*/
class ConvertToDottedProperties extends AbstractPostOrderCallback
implements CompilerPass {
private final AbstractCompiler compiler;
ConvertToDottedProperties(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.GETELEM:
Node left = n.getFirstChild();
Node right = left.getNext();
if (right.getType() == Token.STRING &&
NodeUtil.isValidPropertyName(right.getString())) {
n.removeChild(left);
n.removeChild(right);
parent.replaceChild(n, new Node(Token.GETPROP, left, right));
compiler.reportCodeChange();
}
break;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.parsing.ParserRunner;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Generates an AST for a JavaScript source file.
*
*/
public class JsAst implements SourceAst {
private static final Logger logger_ = Logger.getLogger(JsAst.class.getName());
private static final long serialVersionUID = 1L;
private transient SourceFile sourceFile;
private String fileName;
private Node root;
public JsAst(SourceFile sourceFile) {
this.sourceFile = sourceFile;
this.fileName = sourceFile.getName();
}
@Override
public Node getAstRoot(AbstractCompiler compiler) {
if (root == null) {
createAst(compiler);
}
return root;
}
@Override
public void clearAst() {
root = null;
// While we're at it, clear out any saved text in the source file on
// the assumption that if we're dumping the parse tree, then we probably
// assume regenerating everything else is a smart idea also.
sourceFile.clearCachedSource();
}
@Override
public SourceFile getSourceFile() {
return sourceFile;
}
@Override
public void setSourceFile(SourceFile file) {
Preconditions.checkState(fileName.equals(file.getName()));
sourceFile = file;
}
private void createAst(AbstractCompiler compiler) {
try {
parse(compiler, sourceFile.getName(), sourceFile.getCode());
} catch (IOException e) {
compiler.report(
JSError.make(AbstractCompiler.READ_ERROR, sourceFile.getName()));
}
}
private void parse(AbstractCompiler compiler, String sourceName,
String sourceStr) {
try {
logger_.fine("Parsing: " + sourceName);
root = ParserRunner.parse(sourceName, sourceStr,
compiler.getParserConfig(),
compiler.getDefaultErrorReporter(),
logger_);
} catch (IOException e) {
compiler.report(JSError.make(AbstractCompiler.READ_ERROR, sourceName));
}
if (root == null || compiler.hasHal
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>tingErrors()) {
// There was a parse error or IOException, so use a dummy block.
root = new Node(Token.BLOCK);
} else {
compiler.prepareAst(root);
}
// Set the source name so that the compiler passes can track
// the source file and module.
root.putProp(Node.SOURCENAME_PROP, sourceName);
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
* Group a set of related diagnostic types together, so that they can
* be toggled on and off as one unit.
* @author nicksantos@google.com (Nick Santos)
*/
public class DiagnosticGroup {
// The set of types represented by this group, hashed by key.
private final Set<DiagnosticType> types;
/**
* Create a group that matches all errors of the given types.
*/
public DiagnosticGroup(DiagnosticType ...types) {
this.types = ImmutableSet.copyOf(Arrays.asList(types));
}
/**
* Create a diagnostic group with no name that only matches the given type.
*/
private DiagnosticGroup(DiagnosticType type) {
this.types = ImmutableSet.of(type);
}
// DiagnosticGroups with only a single DiagnosticType.
private static final Map<DiagnosticType, DiagnosticGroup> singletons =
Maps.newHashMap();
/** Create a diagnostic group that matches only the given type. */
static DiagnosticGroup forType(DiagnosticType type) {
if (!singletons.containsKey(type)) {
singletons.put(type, new DiagnosticGroup(type));
}
return singletons.get(type);
}
/**
* Create a composite group.
*/
public DiagnosticGroup(DiagnosticGroup ...groups) {
Set<DiagnosticType> set = Sets.newHashSet();
for (DiagnosticGroup group : groups) {
set.addAll(group.types);
}
this.types = ImmutableSet.copyOf(set);
}
/**
* Returns whether the given error's type matches a type
* in this group.
*/
public boolean matches(JSError error) {
return matches(error.getType());
}
/**
* Returns whether the given type matches a type in this group.
*/
public boolean matches(DiagnosticType type) {
return types.contains(type);
}
/**
* Returns whether all of the types in the given group are in this group.
*/
boolean isSub
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> * properties that are declared in the externs file.
*/
CrossModuleMethodMotion(AbstractCompiler compiler, IdGenerator idGenerator,
boolean canModifyExterns) {
this.compiler = compiler;
this.idGenerator = idGenerator;
this.moduleGraph = compiler.getModuleGraph();
this.analyzer = new AnalyzePrototypeProperties(compiler, moduleGraph,
canModifyExterns, false);
}
public void process(Node externRoot, Node root) {
// If there are < 2 modules, then we will never move anything,
// so we're done.
if (moduleGraph != null && moduleGraph.getModuleCount() > 1) {
analyzer.process(externRoot, root);
moveMethods(analyzer.getAllNameInfo());
}
}
/**
* Move methods deeper in the module graph when possible.
*/
private void moveMethods(Collection<NameInfo> allNameInfo) {
boolean hasStubDeclaration = idGenerator.hasGeneratedAnyIds();
for (NameInfo nameInfo : allNameInfo) {
if (!nameInfo.isReferenced()) {
// The code below can't do anything with unreferenced name
// infos. They should be skipped to avoid NPE since their
// deepestCommonModuleRef is null.
continue;
}
if (nameInfo.readsClosureVariables()) {
continue;
}
JSModule deepestCommonModuleRef = nameInfo.getDeepestCommonModuleRef();
if(deepestCommonModuleRef == null) {
compiler.report(JSError.make(NULL_COMMON_MODULE_ERROR));
continue;
}
Iterator<Symbol> declarations =
nameInfo.getDeclarations().descendingIterator();
while (declarations.hasNext()) {
Symbol symbol = declarations.next();
if (!(symbol instanceof Property)) {
continue;
}
Property prop = (Property) symbol;
// We should only move a property across modules if:
// 1) We can move it deeper in the module graph, and
// 2) it's a function.
//
// #1 should be obvious. #2 is more subtle. It's possible
// to copy off of a prototype, as in the code:
// for (var k in Foo.prototype) {
// doSomethingWith(Foo.prototype[k]);
// }
// This is a common way to implement pseudo-multiple inheritance in JS.
//
// So if we move a prototype method into a deeper module, we must
// replace it with a stub function so that it preserves its original
// behavior.
Node value = prop.getValue();
if (moduleGraph.dependsOn(deepestCommonModuleRef, prop.getModule()) &&
value.getType() == Token.FUNCTION) {
Node valueParent = prop.getValueParent();
Node proto = prop.getPrototype();
int stubId = idGenerator.newId();
// stub out the method in the original module
valueParent.replaceChild(value,
// A.prototype.b = JSCompiler_stubMethod
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(id);
new Node(Token.CALL,
Node.newString(Token.NAME, STUB_METHOD_NAME),
Node.newNumber(stubId))
.copyInformationFromForTree(value));
// unstub the function body in the deeper module
Node unstubParent = compiler.getNodeForCodeInsertion(
deepestCommonModuleRef);
unstubParent.addChildToFront(
// A.prototype.b = JSCompiler_unstubMethod(id, body);
new Node(Token.EXPR_RESULT,
new Node(Token.ASSIGN,
new Node(Token.GETPROP,
proto.cloneTree(),
Node.newString(Token.STRING, nameInfo.name)),
new Node(Token.CALL,
Node.newString(Token.NAME, UNSTUB_METHOD_NAME),
Node.newNumber(stubId),
value)))
.copyInformationFromForTree(value));
compiler.reportCodeChange();
logger.fine("Moved method: " +
proto.getQualifiedName() + "." + nameInfo.name +
" from module " + prop.getModule() + " to module " +
deepestCommonModuleRef);
}
}
}
if (!hasStubDeclaration && idGenerator.hasGeneratedAnyIds()) {
// Declare stub functions in the top-most module.
Node declarations = compiler.parseSyntheticCode(STUB_DECLARATIONS);
compiler.getNodeForCodeInsertion(null).addChildrenToFront(
declarations.removeChildren());
}
}
static class IdGenerator implements Serializable {
private static final long serialVersionUID = 0L;
/**
* Ids for cross-module method stubbing, so that each method has
* a unique id.
*/
private int currentId = 0;
/**
* Returns whether we've generated any new ids.
*/
boolean hasGeneratedAnyIds() {
return currentId != 0;
}
/**
* Creates a new id for stubbing a method.
*/
int newId() {
return currentId++;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> resolve to any JavaScript type. It can only resolve to a named
* {@link JSTypeRegistry} type, or to {@link FunctionType} or
* {@link EnumType}.<p>
*
* If full typedefs are to be supported, then each method on each type class
* needs to be reviewed to make sure that everything works correctly through
* typedefs. Alternatively, we would need to walk through the parse tree and
* unroll each reference to a {@code NamedType} to its resolved type before
* applying the rest of the analysis.<p>
*
* TODO(user): Revisit all of this logic.<p>
*
* The existing typing logic is hacky. Unresolved types should get processed
* in a more consistent way, but with the Rhino merge coming, there will be
* much that has to be changed.<p>
*
*/
class NamedType extends ProxyObjectType {
private static final long serialVersionUID = 1L;
private final String reference;
private final String sourceName;
private final int lineno;
private final int charno;
/**
* Validates the type resolution.
*/
private Predicate<JSType> validator;
/**
* If true, don't warn about unresolveable type names.
*
* NOTE(nicksantos): A lot of third-party code doesn't use our type syntax.
* They have code like
* {@code @return} the bus.
* and they clearly don't mean that "the" is a type. In these cases, we're
* forgiving and try to guess whether or not "the" is a type when it's not
* clear.
*/
private boolean forgiving = false;
/**
* Create a named type based on the reference.
*/
NamedType(JSTypeRegistry registry, String reference,
String sourceName, int lineno, int charno) {
super(registry, registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE));
Preconditions.checkNotNull(reference);
this.reference = reference;
this.sourceName = sourceName;
this.lineno = lineno;
this.charno = charno;
}
@Override
void forgiveUnknownNames() {
forgiving = true;
}
/** Returns the type to which this refers (which is unknown if unresolved). */
public JSType getReferencedType() {
return getReferencedTypeInternal();
}
@Override
public String getReferenceName() {
return reference;
}
@Override
public String toString() {
return reference;
}
@Override
public boolean hasReferenceName() {
return true;
}
@Override
boolean isNamedType() {
return true;
}
@Override
public boolean isNominalType() {
return true;
}
/**
* Two named types are equivalent if they are the same {@code
* ObjectType} object. This is complicated by the fact that isEquivalent
* is sometimes called before we have a chance to resolve the type
* names.
*
*
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> @return {@code true} iff {@code that} == {@code this} or {@code that}
* is a {@link NamedType} whose reference is the same as ours,
* or {@code that} is the type we reference.
*/
@Override
public boolean isEquivalentTo(JSType that) {
if (this == that) {
return true;
}
ObjectType objType = ObjectType.cast(that);
if (objType != null) {
return objType.isNominalType() &&
reference.equals(objType.getReferenceName());
}
return false;
}
@Override
public int hashCode() {
return reference.hashCode();
}
/**
* Resolve the referenced type within the enclosing scope.
*/
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) {
// TODO(user): Investigate whether it is really necessary to keep two
// different mechanisms for resolving named types, and if so, which order
// makes more sense. Now, resolution via registry is first in order to
// avoid triggering the warnings built into the resolution via properties.
boolean resolved = resolveViaRegistry(t, enclosing);
if (detectImplicitPrototypeCycle()) {
handleTypeCycle(t);
}
if (resolved) {
super.resolveInternal(t, enclosing);
return registry.isLastGeneration() ?
getReferencedType() : this;
}
resolveViaProperties(t, enclosing);
if (detectImplicitPrototypeCycle()) {
handleTypeCycle(t);
}
super.resolveInternal(t, enclosing);
return registry.isLastGeneration() ?
getReferencedType() : this;
}
/**
* Resolves a named type by looking it up in the registry.
* @return True if we resolved successfully.
*/
private boolean resolveViaRegistry(
ErrorReporter t, StaticScope<JSType> enclosing) {
JSType type = registry.getType(reference);
if (type != null) {
setReferencedAndResolvedType(type, t, enclosing);
return true;
}
return false;
}
/**
* Resolves a named type by looking up its first component in the scope, and
* subsequent components as properties. The scope must have been fully
* parsed and a symbol table constructed.
*/
private void resolveViaProperties(ErrorReporter t,
StaticScope<JSType> enclosing) {
JSType value = lookupViaProperties(t, enclosing);
// last component of the chain
if ((value instanceof FunctionType) &&
(value.isConstructor() || value.isInterface())) {
FunctionType functionType = (FunctionType) value;
setReferencedAndResolvedType(
functionType.getInstanceType(), t, enclosing);
} else if (value instanceof EnumType) {
setReferencedAndResolvedType(
((EnumType) value).getElementsType(), t, enclosing);
} else {
// We've been running into issues where people forward-declare
// non-named types. (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>This is legitimate...our dependency management
// code doubles as our forward-declaration code.)
//
// So if the type does resolve to an actual value, but it's not named,
// then don't respect the forward declaration.
handleUnresolvedType(t, value == null || value.isUnknownType());
}
}
/**
* Resolves a type by looking up its first component in the scope, and
* subsequent components as properties. The scope must have been fully
* parsed and a symbol table constructed.
* @return The type of the symbol, or null if the type could not be found.
*/
private JSType lookupViaProperties( ErrorReporter t,
StaticScope<JSType> enclosing) {
String[] componentNames = reference.split("\\.", -1);
if (componentNames[0].length() == 0) {
return null;
}
StaticSlot<JSType> slot = enclosing.getSlot(componentNames[0]);
if (slot == null) {
return null;
}
// If the first component has a type of 'Unknown', then any type
// names using it should be regarded as silently 'Unknown' rather than be
// noisy about it.
JSType slotType = slot.getType();
if (slotType == null || slotType.isAllType() || slotType.isNoType()) {
return null;
}
JSType value = getTypedefType(t, slot, componentNames[0]);
if (value == null) {
return null;
}
// resolving component by component
for (int i = 1; i < componentNames.length; i++) {
ObjectType parentClass = ObjectType.cast(value);
if (parentClass == null) {
return null;
}
if (componentNames[i].length() == 0) {
return null;
}
value = parentClass.getPropertyType(componentNames[i]);
}
return value;
}
private void setReferencedAndResolvedType(JSType type, ErrorReporter t,
StaticScope<JSType> enclosing) {
if (validator != null) {
validator.apply(type);
}
setReferencedType(type);
checkEnumElementCycle(t);
setResolvedTypeInternal(getReferencedType());
}
private void handleTypeCycle(ErrorReporter t) {
setReferencedType(
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE));
t.warning("Cycle detected in inheritance chain of type " + reference,
sourceName, lineno, null, charno);
setResolvedTypeInternal(getReferencedType());
}
private void checkEnumElementCycle(ErrorReporter t) {
JSType referencedType = getReferencedType();
if (referencedType instanceof EnumElementType &&
((EnumElementType) referencedType).getPrimitiveType() == this) {
handleTypeCycle(t);
}
}
// Warns about this type being unresolved iff it's not a forward-declared
// type name.
private void handleUnresolvedType(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> ErrorReporter t, boolean ignoreForwardReferencedTypes) {
if (registry.isLastGeneration()) {
boolean isForwardDeclared =
ignoreForwardReferencedTypes &&
registry.isForwardDeclaredType(reference);
boolean beForgiving = forgiving || isForwardDeclared;
if (!beForgiving && registry.isLastGeneration()) {
t.warning("Unknown type " + reference, sourceName, lineno, null,
charno);
} else {
setReferencedType(
registry.getNativeObjectType(
JSTypeNative.CHECKED_UNKNOWN_TYPE));
if (registry.isLastGeneration() && validator != null) {
validator.apply(getReferencedType());
}
}
setResolvedTypeInternal(getReferencedType());
} else {
setResolvedTypeInternal(this);
}
}
JSType getTypedefType(ErrorReporter t, StaticSlot<JSType> slot, String name) {
JSType type = slot.getType();
if (type != null) {
return type;
}
handleUnresolvedType(t, true);
return null;
}
@Override
public boolean setValidator(Predicate<JSType> validator) {
// If the type is already resolved, we can validate it now. If
// the type has not been resolved yet, we need to wait till its
// resolved before we can validate it.
if (this.isResolved()) {
return super.setValidator(validator);
} else {
this.validator = validator;
return true;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* <p>The syntactic scope creator scans the parse tree to create a Scope object
* containing all the variable declarations in that scope.</p>
*
* <p>This implementation is not thread-safe.</p>
*
*/
class SyntacticScopeCreator implements ScopeCreator {
private final AbstractCompiler compiler;
private Scope scope;
private String sourceName;
private final RedeclarationHandler redeclarationHandler;
// The arguments variable is special, in that it's declared in every local
// scope, but not explicitly declared.
private static final String ARGUMENTS = "arguments";
public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR =
DiagnosticType.error(
"JSC_VAR_MULTIPLY_DECLARED_ERROR",
"Variable {0} first declared in {1}");
public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR =
DiagnosticType.error(
"JSC_VAR_ARGUMENTS_SHADOWED_ERROR",
"Shadowing \"arguments\" is not allowed");
/**
* Creates a ScopeCreator.
*/
SyntacticScopeCreator(AbstractCompiler compiler) {
this.compiler = compiler;
this.redeclarationHandler = new DefaultRedeclarationHandler();
}
SyntacticScopeCreator(
AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
public Scope createScope(Node n, Scope parent) {
sourceName = null;
if (parent == null) {
scope = new Scope(n, compiler);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
sourceName = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
if (n.getType() == Token.FUNCTION) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
final Node fnNameNode = n.getFirstChild
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.getType() == Token.LP);
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.getType() == Token.NAME);
declareVar(a);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
private class DefaultRedeclarationHandler implements RedeclarationHandler {
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.getType() == Token.CATCH &&
parent.getType() == Token.CATCH) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = false;
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
if (!allowDupe) {
compiler.report(
JSError.make(sourceName, n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(sourceName, n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
* @param declaredType The variable's type, according to JSDoc
*/
private void declareVar(Node n) {
Preconditions.checkState(n.getType() == Token.NAME);
CompilerInput input = compiler.getInput(sourceName);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(node);
if (inNodes.size() == 1) {
FlowState<L> inNodeState = inNodes.get(0).getAnnotation();
state.setIn(inNodeState.getOut());
} else if (inNodes.size() > 1) {
List<L> values = new ArrayList<L>(inNodes.size());
for (DiGraphNode<N, Branch> currentNode : inNodes) {
FlowState<L> currentNodeState = currentNode.getAnnotation();
values.add(currentNodeState.getOut());
}
state.setIn(joinOp.apply(values));
}
}
} else {
List<DiGraphNode<N, Branch>> inNodes = cfg.getDirectedSuccNodes(node);
if (inNodes.size() == 1) {
DiGraphNode<N, Branch> inNode = inNodes.get(0);
if (inNode == cfg.getImplicitReturn()) {
state.setOut(createEntryLattice());
} else {
FlowState<L> inNodeState = inNode.getAnnotation();
state.setOut(inNodeState.getIn());
}
} else if (inNodes.size() > 1) {
List<L> values = new ArrayList<L>(inNodes.size());
for (DiGraphNode<N, Branch> currentNode : inNodes) {
FlowState<L> currentNodeState = currentNode.getAnnotation();
values.add(currentNodeState.getIn());
}
state.setOut(joinOp.apply(values));
}
}
}
/**
* The in and out states of a node.
*
* @param <L> Input and output lattice element type.
*/
static class FlowState<L extends LatticeElement> implements Annotation {
private L in;
private L out;
/**
* Private constructor. No other classes should create new states.
*
* @param inState Input.
* @param outState Output.
*/
private FlowState(L inState, L outState) {
Preconditions.checkNotNull(inState);
Preconditions.checkNotNull(outState);
this.in = inState;
this.out = outState;
}
L getIn() {
return in;
}
void setIn(L in) {
Preconditions.checkNotNull(in);
this.in = in;
}
L getOut() {
return out;
}
void setOut(L out) {
Preconditions.checkNotNull(out);
this.out = out;
}
@Override
public String toString() {
return String.format("IN: %s OUT: %s", in, out);
}
@Override
public int hashCode() {
return Objects.hashCode(in, out);
}
}
/**
* The exception to be thrown if the analysis has been running for a long
* number of iterations. Chances are the analysis is not monotonic, a
* fixed-point cannot be found and it is currently stuck in an infinite loop
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.
*/
static class MaxIterationsExceededException extends RuntimeException {
private static final long serialVersionUID = 1L;
MaxIterationsExceededException(String msg) {
super(msg);
}
}
abstract static class BranchedForwardDataFlowAnalysis
<N, L extends LatticeElement> extends DataFlowAnalysis<N, L> {
@Override
protected void initialize() {
orderedWorkSet.clear();
for (DiGraphNode<N, Branch> node : getCfg().getDirectedGraphNodes()) {
int outEdgeCount = getCfg().getOutEdges(node.getValue()).size();
List<L> outLattices = Lists.newArrayList();
for (int i = 0; i < outEdgeCount; i++) {
outLattices.add(createInitialEstimateLattice());
}
node.setAnnotation(new BranchedFlowState<L>(
createInitialEstimateLattice(), outLattices));
if (node != getCfg().getImplicitReturn()) {
orderedWorkSet.add(node);
}
}
}
BranchedForwardDataFlowAnalysis(ControlFlowGraph<N> targetCfg,
JoinOp<L> joinOp) {
super(targetCfg, joinOp);
}
/**
* Returns the lattice element at the exit point. Needs to be overridden
* because we use a BranchedFlowState instead of a FlowState; ugh.
*/
@Override
L getExitLatticeElement() {
DiGraphNode<N, Branch> node = getCfg().getImplicitReturn();
BranchedFlowState<L> state = node.getAnnotation();
return state.getIn();
}
@Override
final boolean isForward() {
return true;
}
/**
* The branched flow function maps a single lattice to a list of output
* lattices.
*
* <p>Each outgoing edge of a node will have a corresponding output lattice
* in the ordered returned by
* {@link com.google.javascript.jscomp.graph.DiGraph#getOutEdges(Object)}
* in the returned list.
*
* @return A list of output values depending on the edge's branch type.
*/
abstract List<L> branchedFlowThrough(N node, L input);
@Override
protected final boolean flow(DiGraphNode<N, Branch> node) {
BranchedFlowState<L> state = node.getAnnotation();
List<L> outBefore = state.out;
state.out = branchedFlowThrough(node.getValue(), state.in);
Preconditions.checkState(outBefore.size() == state.out.size());
for (int i = 0; i < outBefore.size(); i++) {
if (!outBefore.get(i).equals(state.out.get(i))) {
return true;
}
}
return false;
}
@Override
protected void joinInputs(DiGraphNode<N, Branch> node) {
BranchedFlowState<L
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>> state = node.getAnnotation();
List<DiGraphNode<N, Branch>> predNodes =
getCfg().getDirectedPredNodes(node);
List<L> values = new ArrayList<L>(predNodes.size());
for (DiGraphNode<N, Branch> predNode : predNodes) {
BranchedFlowState<L> predNodeState = predNode.getAnnotation();
L in = predNodeState.out.get(
getCfg().getDirectedSuccNodes(predNode).indexOf(node));
values.add(in);
}
if (getCfg().getEntry() == node) {
state.setIn(createEntryLattice());
} else if (!values.isEmpty()) {
state.setIn(joinOp.apply(values));
}
}
}
/**
* The in and out states of a node.
*
* @param <L> Input and output lattice element type.
*/
static class BranchedFlowState<L extends LatticeElement>
implements Annotation {
private L in;
private List<L> out;
/**
* Private constructor. No other classes should create new states.
*
* @param inState Input.
* @param outState Output.
*/
private BranchedFlowState(L inState, List<L> outState) {
Preconditions.checkNotNull(inState);
Preconditions.checkNotNull(outState);
this.in = inState;
this.out = outState;
}
L getIn() {
return in;
}
void setIn(L in) {
Preconditions.checkNotNull(in);
this.in = in;
}
List<L> getOut() {
return out;
}
void setOut(List<L> out) {
Preconditions.checkNotNull(out);
for (L item : out) {
Preconditions.checkNotNull(item);
}
this.out = out;
}
@Override
public String toString() {
return String.format("IN: %s OUT: %s", in, out);
}
@Override
public int hashCode() {
return Objects.hashCode(in, out);
}
}
/**
* Compute set of escaped variables. When a variable is escaped in a
* dataflow analysis, it can be reference outside of the code that we are
* analyzing. A variable is escaped if any of the following is true:
*
* <p><ol>
* <li>It is defined as the exception name in CATCH clause so it became a
* variable local not to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates.
* </li>
* <li>Names of named functions because in javascript, <i>function foo(){}</i>
* does not kill <i>foo</i> in the dataflow.</li>
*/
static void computeEscaped(final Scope jsScope, final Set<Var> escaped,
AbstractCompiler compiler) {
// TODO(user):
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Very good place to store this information somewhere.
AbstractPostOrderCallback finder = new AbstractPostOrderCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (jsScope == t.getScope() || !NodeUtil.isName(n)
|| NodeUtil.isFunction(parent)) {
return;
}
String name = n.getString();
Var var = t.getScope().getVar(name);
if (var != null && var.scope == jsScope) {
escaped.add(jsScope.getVar(name));
}
}
};
NodeTraversal t = new NodeTraversal(compiler, finder);
t.traverseAtScope(jsScope);
// 1: Remove the exception name in CATCH which technically isn't local to
// begin with.
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var var = i.next();
if (var.getParentNode().getType() == Token.CATCH ||
compiler.getCodingConvention().isExported(var.getName())) {
escaped.add(var);
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> transformed into {@code compiled}.
*/
private void test(String[] original, String[] compiled) {
test(original, compiled, null);
}
/**
* Asserts that when compiling with the given compiler options,
* {@code original} is transformed into {@code compiled}.
* If {@code warning} is non-null, we will also check if the given
* warning type was emitted.
*/
private void test(String[] original, String[] compiled,
DiagnosticType warning) {
Compiler compiler = compile(original);
if (warning == null) {
assertEquals("Expected no warnings or errors\n" +
"Errors: \n" + Joiner.on("\n").join(compiler.getErrors()) +
"Warnings: \n" + Joiner.on("\n").join(compiler.getWarnings()),
0, compiler.getErrors().length + compiler.getWarnings().length);
} else {
assertEquals(1, compiler.getWarnings().length);
assertEquals(warning, compiler.getWarnings()[0].getType());
}
Node root = compiler.getRoot().getLastChild();
if (useStringComparison) {
assertEquals(Joiner.on("").join(compiled), compiler.toSource());
} else {
Node expectedRoot = parse(compiled);
String explanation = expectedRoot.checkTreeEquals(root);
assertNull("\nExpected: " + compiler.toSource(expectedRoot) +
"\nResult: " + compiler.toSource(root) +
"\n" + explanation, explanation);
}
}
/**
* Asserts that when compiling, there is an error or warning.
*/
private void test(String original, DiagnosticType warning) {
test(new String[] { original }, warning);
}
/**
* Asserts that when compiling, there is an error or warning.
*/
private void test(String[] original, DiagnosticType warning) {
Compiler compiler = compile(original);
assertEquals("Expected exactly one warning or error " +
"Errors: \n" + Joiner.on("\n").join(compiler.getErrors()) +
"Warnings: \n" + Joiner.on("\n").join(compiler.getWarnings()),
1, compiler.getErrors().length + compiler.getWarnings().length);
assertTrue(exitCodes.size() > 0);
int lastExitCode = exitCodes.get(exitCodes.size() - 1);
if (compiler.getErrors().length > 0) {
assertEquals(1, compiler.getErrors().length);
assertEquals(warning, compiler.getErrors()[0].getType());
assertEquals(1, lastExitCode);
} else {
assertEquals(1, compiler.getWarnings().length);
assertEquals(warning, compiler.getWarnings()[0].getType());
assertEquals(0, lastExitCode);
}
}
private CommandLineRunner createCommandLineRunner(String[] original) {
for (int i = 0; i < original.length; i++) {
args.add("--js");
args.add("/path/to/input" + i
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> @throws IOException
*/
public static List<JSSourceFile> getDefaultExterns() throws IOException {
InputStream input = CommandLineRunner.class.getResourceAsStream(
"/externs.zip");
ZipInputStream zip = new ZipInputStream(input);
Map<String, JSSourceFile> externsMap = Maps.newHashMap();
for (ZipEntry entry = null; (entry = zip.getNextEntry()) != null; ) {
LimitInputStream entryStream = new LimitInputStream(zip, entry.getSize());
externsMap.put(entry.getName(),
JSSourceFile.fromInputStream(
// Give the files an odd prefix, so that they do not conflict
// with the user's files.
"externs.zip//" + entry.getName(),
entryStream));
}
Preconditions.checkState(
externsMap.keySet().equals(Sets.newHashSet(DEFAULT_EXTERNS_NAMES)),
"Externs zip must match our hard-coded list of externs.");
// Order matters, so the resources must be added to the result list
// in the expected order.
List<JSSourceFile> externs = Lists.newArrayList();
for (String key : DEFAULT_EXTERNS_NAMES) {
externs.add(externsMap.get(key));
}
return externs;
}
/**
* @return Whether the configuration is valid.
*/
public boolean shouldRunCompiler() {
return this.isConfigValid;
}
/**
* Runs the Compiler. Exits cleanly in the event of an error.
*/
public static void main(String[] args) {
CommandLineRunner runner = new CommandLineRunner(args);
if (runner.shouldRunCompiler()) {
runner.run();
} else {
System.exit(-1);
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>> dest = getNodeOrFail(destValue);
LinkedUndirectedGraphEdge<N, E> edge =
useEdgeAnnotations ?
new AnnotatedLinkedUndirectedGraphEdge<N, E>(src, edgeValue, dest) :
new LinkedUndirectedGraphEdge<N, E>(src, edgeValue, dest);
src.getNeighborEdges().add(edge);
dest.getNeighborEdges().add(edge);
}
@Override
public void disconnect(N srcValue, N destValue) {
LinkedUndirectedGraphNode<N, E> src = getNodeOrFail(srcValue);
LinkedUndirectedGraphNode<N, E> dest = getNodeOrFail(destValue);
for (UndiGraphEdge<N, E> edge :
getUndirectedGraphEdges(srcValue, destValue)) {
src.getNeighborEdges().remove(edge);
dest.getNeighborEdges().remove(edge);
}
}
@Override
public UndiGraphNode<N, E> createUndirectedGraphNode(
N nodeValue) {
LinkedUndirectedGraphNode<N, E> node = nodes.get(nodeValue);
if (node == null) {
node = useNodeAnnotations ?
new AnnotatedLinkedUndirectedGraphNode<N, E>(nodeValue) :
new LinkedUndirectedGraphNode<N, E>(nodeValue);
nodes.put(nodeValue, node);
}
return node;
}
@Override
public List<GraphNode<N, E>> getNeighborNodes(N value) {
UndiGraphNode<N, E> uNode = getUndirectedGraphNode(value);
List<GraphNode<N, E>> nodeList = Lists.newArrayList();
for (Iterator<GraphNode<N, E>> i = getNeighborNodesIterator(value);
i.hasNext();) {
nodeList.add(i.next());
}
return nodeList;
}
@Override
public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) {
UndiGraphNode<N, E> uNode = getUndirectedGraphNode(value);
Preconditions.checkNotNull(uNode, value + " should be in the graph.");
return ((LinkedUndirectedGraphNode<N, E>) uNode).neighborIterator();
}
@SuppressWarnings("unchecked")
@Override
public List<UndiGraphEdge<N, E>> getUndirectedGraphEdges(N n1, N n2) {
UndiGraphNode<N, E> dNode1 = nodes.get(n1);
if (dNode1 == null) {
return null;
}
UndiGraphNode<N, E> dNode2 = nodes.get(n2);
if (dNode2 == null) {
return null;
}
List<UndiGraphEdge<N, E>> edges = Lists.newArrayList();
for (UndiGraphEdge<N, E> outEdge : dNode1.getNeighborEdges()) {
if (outEdge.getNodeA() == dNode2 ||
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import java.nio.charset.Charset;
import java.util.Set;
/**
* A code generator that outputs type annotations for functions and
* constructors.
*/
class TypedCodeGenerator extends CodeGenerator {
TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) {
super(consumer, outputCharset);
}
@Override
void add(Node n, Context context) {
Node parent = n.getParent();
if (parent != null
&& (parent.getType() == Token.BLOCK
|| parent.getType() == Token.SCRIPT)) {
if (n.getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n));
} else if (n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN) {
Node rhs = n.getFirstChild().getLastChild();
add(getTypeAnnotation(rhs));
} else if (n.getType() == Token.VAR
&& n.getFirstChild().getFirstChild() != null
&& n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) {
add(getFunctionAnnotation(n.getFirstChild().getFirstChild()));
}
}
super.add(n, context);
}
private String getTypeAnnotation(Node node) {
JSType type = node.getJSType();
if (type instanceof FunctionType) {
return getFunctionAnnotation(node);
} else if (type != null && !type.isUnknownType()
&& !type.isEmptyType() && !type.isVoidType() &&
!type.isFunctionPrototypeType()) {
return "/** @type {" + node.getJSType() + "} */\n";
} else {
return "";
}
}
/**
* @param fnNode A node for a function for which to generate a type annotation
*/
private String getFunctionAnnotation(Node fnNode)
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {
Preconditions.checkState(fnNode.getType() == Token.FUNCTION);
StringBuilder sb = new StringBuilder("/**\n");
JSType type = fnNode.getJSType();
if (type == null || type.isUnknownType()) {
return "";
}
FunctionType funType = (FunctionType) fnNode.getJSType();
// We need to use the child nodes of the function as the nodes for the
// parameters of the function type do not have the real parameter names.
// FUNCTION
// NAME
// LP
// NAME param1
// NAME param2
if (fnNode != null) {
Node paramNode = NodeUtil.getFnParameters(fnNode).getFirstChild();
// Param types
for (Node n : funType.getParameters()) {
// Bail out if the paramNode is not there.
if (paramNode == null) {
break;
}
sb.append(" * @param {" + getParameterNodeJSDocType(n) + "} ");
sb.append(paramNode.getString());
sb.append("\n");
paramNode = paramNode.getNext();
}
}
// Return type
JSType retType = funType.getReturnType();
if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) {
sb.append(" * @return {" + retType + "}\n");
}
// Constructor/interface
if (funType.isConstructor() || funType.isInterface()) {
FunctionType superConstructor = funType.getSuperClassConstructor();
if (superConstructor != null) {
ObjectType superInstance =
funType.getSuperClassConstructor().getInstanceType();
if (!superInstance.toString().equals("Object")) {
sb.append(" * @extends {" + superInstance + "}\n");
}
}
// Avoid duplicates, add implemented type to a set first
Set<String> interfaces = Sets.newTreeSet();
for (ObjectType interfaze : funType.getImplementedInterfaces()) {
interfaces.add(interfaze.toString());
}
for (String interfaze : interfaces) {
sb.append(" * @implements {" + interfaze + "}\n");
}
if (funType.isConstructor()) {
sb.append(" * @constructor\n");
} else if (funType.isInterface()) {
sb.append(" * @interface\n");
}
}
if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) {
sb.append(" * @javadispatch\n");
}
sb.append(" */\n");
return sb.toString();
}
/**
* Creates a JSDoc-suitable String representation the type of a parameter.
*
* @param parameterNode The parameter node.
*/
private String getParameterNodeJSDocType(Node parameterNode) {
JSType parameterType = parameterNode.getJSType();
String typeString;
// Emit unknown types
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Find all Functions, VARs, and Exception names and make them
* unique. Specifically, it will not modify object properties.
* @author johnlenz@google.com (John Lenz)
* TODO(johnlenz): Try to merge this with the ScopeCreator.
*/
class MakeDeclaredNamesUnique
implements NodeTraversal.ScopedCallback {
public static final String ARGUMENTS = "arguments";
private Deque<Renamer> nameStack = new ArrayDeque<Renamer>();
private final Renamer rootRenamer;
MakeDeclaredNamesUnique() {
this(new ContextualRenamer());
}
MakeDeclaredNamesUnique(Renamer renamer) {
this.rootRenamer = renamer;
}
static CompilerPass getContextualRenameInverter(AbstractCompiler compiler) {
return new ContextualRenameInverter(compiler);
}
@Override
public void enterScope(NodeTraversal t) {
Node declarationRoot = t.getScopeRoot();
Renamer renamer;
if (nameStack.isEmpty()) {
// If the contextual renamer is being used the starting context can not
// be a function.
Preconditions.checkState(
declarationRoot.getType() != Token.FUNCTION ||
!(rootRenamer instanceof ContextualRenamer));
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
Preconditions.checkState(t.inGlobalScope());
renamer = rootRenamer;
} else {
renamer = nameStack.peek().forChildScope();
}
if (declarationRoot.getType() == Token.FUNCTION) {
// Add the function parameters
Node fnParams = declarationRoot.getFirstChild().getNext();
for (Node c = fnParams.getFirstChild(); c != null; c = c.getNext()) {
String name = c.getString();
renamer.addDeclaredName(name);
}
// Add the function body declarations
Node functionBody = declarationRoot.getLastChild();
findDeclaredNames(functionBody, null, renamer);
} else {
// Add the block declarations
findDeclaredNames(declarationRoot, null, renamer);
}
nameStack.push(renamer);
}
@Override
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
nameStack.pop();
}
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.FUNCTION:
{
// Add recursive function name, if needed.
// NOTE: "enterScope" is called after we need to pick up this name.
Renamer renamer = nameStack.peek().forChildScope();
// If needed, add the function recursive name.
String name = n.getFirstChild().getString();
if (name != null && !name.isEmpty() && parent != null
&& !NodeUtil.isFunctionDeclaration(n)) {
renamer.addDeclaredName(name);
}
nameStack.push(renamer);
}
break;
case Token.CATCH:
{
Renamer renamer = nameStack.peek().forChildScope();
String name = n.getFirstChild().getString();
renamer.addDeclaredName(name);
nameStack.push(renamer);
}
break;
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
String newName = getReplacementName(n.getString());
if (newName != null) {
Renamer renamer = nameStack.peek();
if (renamer.stripConstIfReplaced()) {
// TODO(johnlenz): Do we need to do anything about the javadoc?
n.removeProp(Node.IS_CONSTANT_NAME);
}
n.setString(newName);
t.getCompiler().reportCodeChange();
}
break;
case Token.FUNCTION:
// Remove function recursive name (if any).
nameStack.pop();
break;
case Token.CATCH:
// Remove catch except name from the stack of names.
nameStack.pop();
break;
}
}
/**
* Walks the stack of name maps and finds the replacement name for the
* current scope.
*/
private String get
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ReplacementName(String oldName) {
for (Renamer names : nameStack) {
String newName = names.getReplacementName(oldName);
if (newName != null) {
return newName;
}
}
return null;
}
/**
* Traverses the current scope and collects declared names. Does not
* decent into functions or add CATCH exceptions.
*/
private void findDeclaredNames(Node n, Node parent, Renamer renamer) {
// Do a shallow traversal, so don't traverse into function declarations,
// except for the name of the function itself.
if (parent == null
|| parent.getType() != Token.FUNCTION
|| n == parent.getFirstChild()) {
if (NodeUtil.isVarDeclaration(n)) {
renamer.addDeclaredName(n.getString());
} else if (NodeUtil.isFunctionDeclaration(n)) {
Node nameNode = n.getFirstChild();
renamer.addDeclaredName(nameNode.getString());
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
findDeclaredNames(c, n, renamer);
}
}
}
/**
* Declared names renaming policy interface.
*/
interface Renamer {
/**
* Called when a declared name is found in the local current scope.
*/
void addDeclaredName(String name);
/**
* @return A replacement name, null if oldName is unknown or should not
* be replaced.
*/
String getReplacementName(String oldName);
/**
* @return Whether the constant-ness of a name should be removed.
*/
boolean stripConstIfReplaced();
/**
* @return A Renamer for a scope within the scope of the current Renamer.
*/
Renamer forChildScope();
}
/**
* Inverts the transformation by {@link ContextualRenamer}, when possible.
*/
static class ContextualRenameInverter
implements ScopedCallback, CompilerPass {
private final AbstractCompiler compiler;
// The set of names referenced in the current scope.
private Set<String> referencedNames = ImmutableSet.of();
// Stack reference sets.
private Deque<Set<String>> referenceStack = new ArrayDeque<Set<String>>();
// Name are globally unique initially, so we don't need a per-scope map.
private Map<String, List<Node>> nameMap = Maps.newHashMap();
private ContextualRenameInverter(AbstractCompiler compiler) {
this.compiler = compiler;
}
public void process(Node externs, Node js) {
NodeTraversal.traverse(compiler, js, this);
}
public static String getOrginalName(String name) {
int index = indexOfSeparator(name);
return (index == -1) ? name : name.substring(0, index);
}
private static int indexOfSeparator(String name) {
return name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR);
}
private boolean containsSeparator(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>String name) {
return name.indexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR) != -1;
}
/**
* Prepare a set for the new scope.
*/
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
referenceStack.push(referencedNames);
referencedNames = Sets.newHashSet();
}
/**
* Rename vars for the current scope, and merge any referenced
* names into the parent scope reference set.
*/
public void exitScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) {
Var v = it.next();
handleScopeVar(v);
}
// Merge any names that were referenced but not declared in the current
// scope.
Set<String> current = referencedNames;
referencedNames = referenceStack.pop();
// If there isn't anything left in the stack we will be going into the
// global scope: don't try to build a set of referenced names for the
// global scope.
if (!referenceStack.isEmpty()) {
referencedNames.addAll(current);
}
}
/**
* For the Var declared in the current scope determine if it is possible
* to revert the name to its orginal form without conflicting with other
* values.
*/
void handleScopeVar(Var v) {
String name = v.getName();
if (containsSeparator(name) && !getOrginalName(name).isEmpty()) {
String newName = findReplacementName(name);
referencedNames.remove(name);
// Adding a reference to the new name to prevent either the parent
// scopes or the current scope renaming another var to this new name.
referencedNames.add(newName);
List<Node> references = nameMap.get(name);
Preconditions.checkState(references != null);
for (Node n : references) {
Preconditions.checkState(n.getType() == Token.NAME);
n.setString(newName);
}
compiler.reportCodeChange();
nameMap.remove(name);
}
}
/**
* Find a name usable in the local scope.
*/
private String findReplacementName(String name) {
String original = getOrginalName(name);
String newName = original;
int i = 0;
while (!isValidName(newName)) {
newName = original +
ContextualRenamer.UNIQUE_ID_SEPARATOR + String.valueOf(i++);
}
return newName;
}
/**
* @return Whether the name is valid to use in the local scope.
*/
private boolean isValidName(String name) {
if (TokenStream.isJSIdentifier(name) &&
!referencedNames.contains(name) &&
!name.equals(ARGUMENTS)) {
return true;
}
return false;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
}
private void reserveName(String name) {
nameUsage.setCount(name, 0, 1);
}
private int incrementNameCount(String name) {
return nameUsage.add(name, 1);
}
@Override
public boolean stripConstIfReplaced() {
return false;
}
}
/**
* Rename every declared name to be unique. Typically this would be used
* when injecting code to insure that names do not conflict with existing
* names.
*
* Used by the FunctionInjector
* @see FunctionInjector
*/
static class InlineRenamer implements Renamer {
private final Map<String, String> declarations = Maps.newHashMap();
private final Supplier<String> uniqueIdSupplier;
private final String idPrefix;
private final boolean removeConstness;
InlineRenamer(
Supplier<String> uniqueIdSupplier,
String idPrefix,
boolean removeConstness) {
this.uniqueIdSupplier = uniqueIdSupplier;
// To ensure that the id does not conflict with the id from the
// ContextualRenamer some prefix is needed.
Preconditions.checkArgument(!idPrefix.isEmpty());
this.idPrefix = idPrefix;
this.removeConstness = removeConstness;
}
@Override
public void addDeclaredName(String name) {
Preconditions.checkState(!name.equals(ARGUMENTS));
if (!declarations.containsKey(name)) {
declarations.put(name, getUniqueName(name));
}
}
private String getUniqueName(String name) {
if (name.isEmpty()) {
return name;
}
if (name.indexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR) != -1) {
name = name.substring(
0, name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR));
}
// By using the same separator the id will be stripped if it isn't
// needed when variable renaming is turned off.
return name + ContextualRenamer.UNIQUE_ID_SEPARATOR
+ idPrefix + uniqueIdSupplier.get();
}
@Override
public String getReplacementName(String oldName) {
return declarations.get(oldName);
}
@Override
public Renamer forChildScope() {
return new InlineRenamer(uniqueIdSupplier, idPrefix, removeConstness);
}
@Override
public boolean stripConstIfReplaced() {
return removeConstness;
}
}
/**
* For injecting boilerplate libraries. Leaves global names alone
* and renames local names like InlineRenamer.
*/
static class BoilerplateRenamer extends ContextualRenamer {
private final Supplier<String> uniqueIdSupplier;
private final String idPrefix;
BoilerplateRenamer(
Supplier<String> uniqueIdSupplier,
String idPrefix) {
this.uniqueIdSupplier = uniqueIdSupplier;
this.idPrefix = idPrefix;
}
@Override
public Renamer forChildScope() {
return new InlineRenamer(uniqueIdSupplier, idPrefix
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Check for invalid breaks and continues in the program.
*
*/
class ControlStructureCheck implements CompilerPass {
private AbstractCompiler compiler;
private String sourceName = null;
static final DiagnosticType USE_OF_WITH = DiagnosticType.warning(
"JSC_USE_OF_WITH",
"The use of the 'with' structure should be avoided.");
ControlStructureCheck(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
check(root);
}
/**
* Reports errors for any invalid use of control structures.
*
* @param node Current node to check.
*/
private void check(Node node) {
switch (node.getType()) {
case Token.WITH:
JSDocInfo info = node.getJSDocInfo();
boolean allowWith =
info != null && info.getSuppressions().contains("with");
if (!allowWith) {
report(node, USE_OF_WITH);
}
break;
case Token.SCRIPT:
// Remember the source file name in case we need to report an error.
sourceName = (String) node.getProp(Node.SOURCENAME_PROP);
break;
}
for (Node bChild = node.getFirstChild(); bChild != null;) {
Node next = bChild.getNext();
check(bChild);
bChild = next;
}
}
private void report(Node n, DiagnosticType error) {
compiler.report(JSError.make(sourceName, n, error));
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ForIn(nextSibling)) {
Node forNode = nextSibling;
Node forVar = forNode.getFirstChild();
if (NodeUtil.isName(forVar)
&& NodeUtil.isVar(n) && n.hasOneChild()) {
Node name = n.getFirstChild();
if (!name.hasChildren()
&& forVar.getString().equals(name.getString())) {
// Ok, the names match, and the var declaration does not have an
// initializer. Move it into the loop.
parent.removeChild(n);
forNode.replaceChild(forVar, n);
compiler.reportCodeChange();
}
}
} else if (nextSibling.getType() == Token.FOR
&& nextSibling.getFirstChild().getType() == Token.EMPTY) {
// Does the current node contain an in operator? If so, embedding
// the expression in a for loop can cause some Javascript parsers (such
// as the Playstation 3's browser based on Access's NetFront
// browser) to fail to parse the code.
// See bug 1778863 for details.
if (NodeUtil.containsType(n, Token.IN)) {
return;
}
// Move the current node into the FOR loop initializer.
Node forNode = nextSibling;
Node oldInitializer = forNode.getFirstChild();
parent.removeChild(n);
Node newInitializer;
if (NodeUtil.isVar(n)) {
newInitializer = n;
} else {
// Extract the expression from EXPR_RESULT node.
Preconditions.checkState(n.hasOneChild());
newInitializer = n.getFirstChild();
n.removeChild(newInitializer);
}
forNode.replaceChild(oldInitializer, newInitializer);
compiler.reportCodeChange();
}
}
static class StripConstantAnnotations
extends AbstractPostOrderCallback
implements CompilerPass {
private AbstractCompiler compiler;
StripConstantAnnotations(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node js) {
NodeTraversal.traverse(compiler, externs, this);
NodeTraversal.traverse(compiler, js, this);
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
if (node.getType() == Token.NAME || node.getType() == Token.STRING) {
node.removeProp(Node.IS_CONSTANT_NAME);
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> been deprecated: {2}");
static final DiagnosticType DEPRECATED_CLASS = DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS",
"Class {0} has been deprecated.");
static final DiagnosticType DEPRECATED_CLASS_REASON = DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS_REASON",
"Class {0} has been deprecated: {1}");
static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_GLOBAL_ACCESS",
"Access to private variable {0} not allowed outside file {1}.");
static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_PROPERTY_ACCESS",
"Access to private property {0} of {1} not allowed here.");
static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PROTECTED_PROPERTY_ACCESS",
"Access to protected property {0} of {1} not allowed here.");
static final DiagnosticType PRIVATE_OVERRIDE =
DiagnosticType.disabled(
"JSC_PRIVATE_OVERRIDE",
"Overriding private property of {0}.");
static final DiagnosticType VISIBILITY_MISMATCH =
DiagnosticType.disabled(
"JSC_VISIBILITY_MISMATCH",
"Overriding {0} property of {1} with {2} property.");
private final AbstractCompiler compiler;
private final TypeValidator validator;
// State about the current traversal.
private int deprecatedDepth = 0;
private int methodDepth = 0;
private JSType currentClass = null;
CheckAccessControls(AbstractCompiler compiler) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
}
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth++;
}
if (methodDepth == 0) {
currentClass = getClassOfMethod(n, parent);
}
methodDepth++;
}
}
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth--;
}
methodDepth--;
if (methodDepth == 0) {
currentClass = null;
}
}
}
/**
* Gets the type of the class that "owns" a method, or null if
* we know that its un-owned.
*/
private JSType getClassOfMethod(Node n, Node parent) {
if (parent.getType() == Token.ASSIGN) {
Node lValue = parent.getFirstChild();
if (lValue.isQualifiedName()) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
if (lValue.getType() == Token.GETPROP) {
// We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return ((FunctionType) lValueType).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.getType() == Token.NAME) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isConstructor()) {
return ((FunctionType) type).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR ||
parent.getType() == Token.NEW) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.getType() == Token.NEW) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE &&
!t.getInput().getName().equals(docInfo.getSourceName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), docInfo.getSourceName()));
}
}
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType != null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = t.inGlobalScope() &&
parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(propertyName);
if (docInfo != null &&
docInfo.getVisibility() != Visibility.INHERITED) {
break;
}
}
if (objectType == null) {
// We couldn't find a visibility modifier; assume it's public.
return;
}
boolean sameInput =
t.getInput().getName().equals(docInfo.getSourceName());
Visibility visibility = docInfo.getVisibility();
JSType ownerType = normalizeClassType(objectType);
if (isOverride) {
// Check an ASSIGN statement that's trying to override a property
// on a superclass.
JSDocInfo overridingInfo = parent.getJSDocInfo();
Visibility overridingVisibility = overridingInfo == null ?
Visibility.INHERITED : overridingInfo.getVisibility();
// Check that (a) the property *can* be overridden, and
// (b) that the visibility of the override is the same as the
// visibility of the original property.
if (visibility == Visibility.PRIVATE && !sameInput) {
compiler.report(
t.makeError(getprop, PRIVATE_OVERRIDE,
objectType.toString()));
} else if (overridingVisibility != Visibility.INHERITED &&
overridingVisibility != visibility) {
compiler.report(
t.makeError(getprop, VISIBILITY_MISMATCH,
visibility.name(), objectType.toString(),
overridingVisibility.name()));
}
} else {
if (sameInput) {
// private access is always allowed in the same file.
return;
} else if (visibility == Visibility.PRIVATE &&
(currentClass == null || ownerType.differsFrom(currentClass))) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
// private access is not allowed outside the file
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> from a different
// enclosing class.
compiler.report(
t.makeError(getprop,
BAD_PRIVATE_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
} else if (visibility == Visibility.PROTECTED) {
// There are 3 types of legal accesses of a protected property:
// 1) Accesses in the same file
// 2) Overriding the property in a subclass
// 3) Accessing the property from inside a subclass
// The first two have already been checked for.
if (currentClass == null || !currentClass.isSubtype(ownerType)) {
compiler.report(
t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
}
}
}
}
}
/**
* Whether the given access of a private constructor is legal.
*
* For example,
* new PrivateCtor_(); // not legal
* PrivateCtor_.newInstance(); // legal
* x instanceof PrivateCtor_ // legal
*
* This is a weird special case, because our visibility system is inherited
* from Java, and JavaScript has no distinction between classes and
* constructors like Java does.
*
* We may want to revisit this if we decide to make the restrictions tighter.
*/
private static boolean isValidPrivateConstructorAccess(Node parent) {
return parent.getType() != Token.NEW;
}
/**
* Determines whether a deprecation warning should be emitted.
* @param t The current traversal.
* @param n The node which we are checking.
* @param parent The parent of the node which we are checking.
*/
private boolean shouldEmitDeprecationWarning(
NodeTraversal t, Node n, Node parent) {
// In the global scope, there are only two kinds of accesses that should
// be flagged for warnings:
// 1) Calls of deprecated functions and methods.
// 2) Instantiations of deprecated classes.
// For now, we just let everything else by.
if (t.inGlobalScope()) {
if (!((parent.getType() == Token.CALL && parent.getFirstChild() == n) ||
n.getType() == Token.NEW)) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.getType() == Token.GETPROP && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently ok to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.getType() == Token.ASSIGN &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type) {
if (type == null) {
return null;
}
JSDocInfo info = type.getJSDocInfo();
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
ObjectType implicitProto = objType.getImplicitPrototype();
if (implicitProto != null) {
return getTypeDeprecationInfo(implicitProto);
}
}
return null;
}
/**
* Returns the deprecation reason for the property if it is marked
* as being deprecated. Returns empty string if the property is deprecated
* but no reason was given. Returns null if the property is not deprecated.
*/
private static String getPropertyDeprecationInfo(ObjectType type,
String prop) {
JSDocInfo info = type.getOwnPropertyJSDocInfo(prop);
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType implicitProto = type.getImplicitPrototype();
if (implicitProto != null) {
return getPropertyDeprecationInfo(implicitProto, prop);
}
return null;
}
/**
* Dereference a type, autoboxing it and filtering out null.
*/
private static JSType dereference(JSType type) {
return type == null ? null : type.dereference();
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>PassFactory pass : passes) {
Preconditions.checkState(pass.isOneTimePass());
}
}
/** Verify that all the passes are multi-run passes. */
private void assertAllLoopablePasses(List<PassFactory> passes) {
for (PassFactory pass : passes) {
Preconditions.checkState(!pass.isOneTimePass());
}
}
/** Checks for validity of the control structures. */
private final PassFactory checkControlStructures =
new PassFactory("checkControlStructures", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new ControlStructureCheck(compiler);
}
};
/** Checks that all constructed classes are goog.require()d. */
private final PassFactory checkRequires =
new PassFactory("checkRequires", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new CheckRequiresForConstructors(compiler, options.checkRequires);
}
};
/** Makes sure @constructor is paired with goog.provides(). */
private final PassFactory checkProvides =
new PassFactory("checkProvides", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new CheckProvides(compiler, options.checkProvides);
}
};
private static final DiagnosticType GENERATE_EXPORTS_ERROR =
DiagnosticType.error(
"JSC_GENERATE_EXPORTS_ERROR",
"Exports can only be generated if export symbol/property " +
"functions are set.");
/** Generates exports for @export annotations. */
private final PassFactory generateExports =
new PassFactory("generateExports", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
CodingConvention convention = compiler.getCodingConvention();
if (convention.getExportSymbolFunction() != null &&
convention.getExportPropertyFunction() != null) {
return new GenerateExports(compiler,
convention.getExportSymbolFunction(),
convention.getExportPropertyFunction());
} else {
return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR);
}
}
};
/** Generates exports for functions associated with JSUnit. */
private final PassFactory exportTestFunctions =
new PassFactory("exportTestFunctions", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
CodingConvention convention = compiler.getCodingConvention();
if (convention.getExportSymbolFunction() != null) {
return new ExportTestFunctions(compiler,
convention.getExportSymbolFunction());
} else {
return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR);
}
}
};
/** Raw exports processing pass. */
final PassFactory gatherRawExports =
new PassFactory("gatherRawExports", false) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
final GatherRawExports pass = new GatherRawExports(
compiler);
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
pass.process(externs,
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> root);
if (exportedNames == null) {
exportedNames = Sets.newHashSet();
}
exportedNames.addAll(pass.getExportedVariableNames());
}
};
}
};
/** Closure pre-processing pass. */
@SuppressWarnings("deprecation")
final PassFactory closurePrimitives =
new PassFactory("processProvidesAndRequires", false) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
final ProcessClosurePrimitives pass = new ProcessClosurePrimitives(
compiler,
options.brokenClosureRequiresLevel,
options.rewriteNewDateGoogNow);
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
pass.process(externs, root);
exportedNames = pass.getExportedVariableNames();
}
};
}
};
/**
* The default i18n pass.
* A lot of the options are not configurable, because ReplaceMessages
* has a lot of legacy logic.
*/
private final PassFactory replaceMessages =
new PassFactory("replaceMessages", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new ReplaceMessages(compiler,
options.messageBundle,
/* warn about message dupes */
true,
/* allow messages with goog.getMsg */
JsMessage.Style.getFromParams(true, false),
/* if we can't find a translation, don't worry about it. */
false);
}
};
/** Applies aliases and inlines goog.scope. */
final PassFactory closureGoogScopeAliases =
new PassFactory("processGoogScopeAliases", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new ScopedAliases(compiler);
}
};
/** Checks that CSS class names are wrapped in goog.getCssName */
private final PassFactory closureCheckGetCssName =
new PassFactory("checkMissingGetCssName", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
String blacklist = options.checkMissingGetCssNameBlacklist;
Preconditions.checkState(blacklist != null && !blacklist.isEmpty(),
"Not checking use of goog.getCssName because of empty blacklist.");
return new CheckMissingGetCssName(
compiler, options.checkMissingGetCssNameLevel, blacklist);
}
};
/**
* Processes goog.getCssName. The cssRenamingMap is used to lookup
* replacement values for the classnames. If null, the raw class names are
* inlined.
*/
private final PassFactory closureReplaceGetCssName =
new PassFactory("renameCssNames", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
Map<String, Integer> newCssNames = null;
if (options.gatherCssNames) {
newCssNames = Maps.newHashMap();
}
(new ReplaceCssNames(compiler, new
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
final PassFactory inferTypes =
new PassFactory("inferTypes", false) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(typedScopeCreator);
makeTypeInference(compiler).process(externs, root);
}
};
}
};
/** Checks type usage */
private final PassFactory checkTypes =
new PassFactory("checkTypes", false) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node root) {
Preconditions.checkNotNull(topScope);
Preconditions.checkNotNull(typedScopeCreator);
TypeCheck check = makeTypeCheck(compiler);
check.process(externs, root);
compiler.getErrorManager().setTypedPercent(check.getTypedPercent());
}
};
}
};
/**
* Checks possible execution paths of the program for problems: missing return
* statements and dead code.
*/
private final PassFactory checkControlFlow =
new PassFactory("checkControlFlow", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
List<Callback> callbacks = Lists.newArrayList();
if (options.checkUnreachableCode.isOn()) {
callbacks.add(
new CheckUnreachableCode(compiler, options.checkUnreachableCode));
}
if (options.checkMissingReturn.isOn() && options.checkTypes) {
callbacks.add(
new CheckMissingReturn(compiler, options.checkMissingReturn));
}
return combineChecks(compiler, callbacks);
}
};
/** Checks access controls. Depends on type-inference. */
private final PassFactory checkAccessControls =
new PassFactory("checkAccessControls", true) {
@Override
protected CompilerPass createInternal(AbstractCompiler compiler) {
return new CheckAccessControls(compiler);
}
};
/** Executes the given callbacks with a {@link CombinedCompilerPass}. */
private static CompilerPass combineChecks(AbstractCompiler compiler,
List<Callback> callbacks) {
Preconditions.checkArgument(callbacks.size() > 0);
Callback[] array = callbacks.toArray(new Callback[callbacks.size()]);
return new CombinedCompilerPass(compiler, array);
}
/** A compiler pass that resolves types in the global scope. */
private class GlobalTypeResolver implements CompilerPass {
private final AbstractCompiler compiler;
GlobalTypeResolver(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (topScope == null) {
regenerateGlobalTypedScope(compiler, root.getParent());
} else {
compiler.getTypeRegistry().resolveTypesInScope(topScope);
}
}
}
/** Checks global name usage. */
private final PassFactory checkGlobalNames =
new PassFactory("Check names", true) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>COMPILED_CONSTANT_NAME, new Node(Token.TRUE));
}
if (options.closurePass && options.locale != null) {
additionalReplacements.put(CLOSURE_LOCALE_CONSTANT_NAME,
Node.newString(options.locale));
}
return additionalReplacements;
}
private final PassFactory printNameReferenceGraph =
new PassFactory("printNameReferenceGraph", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
NameReferenceGraphConstruction gc =
new NameReferenceGraphConstruction(compiler);
gc.process(externs, jsRoot);
String graphFileName = options.nameReferenceGraphPath;
try {
Files.write(DotFormatter.toDot(gc.getNameReferenceGraph()),
new File(graphFileName),
Charsets.UTF_8);
} catch (IOException e) {
compiler.report(
JSError.make(
NAME_REF_GRAPH_FILE_ERROR, e.getMessage(), graphFileName));
}
}
};
}
};
private final PassFactory printNameReferenceReport =
new PassFactory("printNameReferenceReport", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override
public void process(Node externs, Node jsRoot) {
NameReferenceGraphConstruction gc =
new NameReferenceGraphConstruction(compiler);
String reportFileName = options.nameReferenceReportPath;
try {
NameReferenceGraphReport report =
new NameReferenceGraphReport(gc.getNameReferenceGraph());
Files.write(report.getHtmlReport(),
new File(reportFileName),
Charsets.UTF_8);
} catch (IOException e) {
compiler.report(
JSError.make(
NAME_REF_REPORT_FILE_ERROR, e.getMessage(), reportFileName));
}
}
};
}
};
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* A builder for the Rhino Node representing Function parameters.
* @author nicksantos@google.com (Nick Santos)
*/
public class FunctionParamBuilder {
private final JSTypeRegistry registry;
private final Node root = new Node(Token.LP);
public FunctionParamBuilder(JSTypeRegistry registry) {
this.registry = registry;
}
/**
* Add parameters of the given type to the end of the param list.
* @return False if this is called after optional params are added.
*/
public boolean addRequiredParams(JSType ...types) {
if (hasOptionalOrVarArgs()) {
return false;
}
for (JSType type : types) {
newParameter(type);
}
return true;
}
/**
* Add optional parameters of the given type to the end of the param list.
* @param types Types for each optional parameter. The builder will make them
* undefineable.
* @return False if this is called after var
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> args are added.
*/
public boolean addOptionalParams(JSType ...types) {
if (hasVarArgs()) {
return false;
}
for (JSType type : types) {
newParameter(registry.createOptionalType(type)).setOptionalArg(true);
}
return true;
}
/**
* Add variable arguments to the end of the parameter list.
* @return False if this is called after var args are added.
*/
public boolean addVarArgs(JSType type) {
if (hasVarArgs()) {
return false;
}
// There are two types of variable argument functions:
// 1) Programmer-defined var args
// 2) Native bottom types that can accept any argument.
// For the first one, "undefined" is a valid value for all arguments.
// For the second, we do not want to cast it up to undefined.
if (!type.isEmptyType()) {
type = registry.createOptionalType(type);
}
newParameter(type).setVarArgs(true);
return true;
}
/**
* Copies the parameter specification from the given node.
*/
public Node newParameterFromNode(Node n) {
Node newParam = newParameter(n.getJSType());
newParam.setVarArgs(n.isVarArgs());
newParam.setOptionalArg(n.isOptionalArg());
return newParam;
}
// Add a parameter to the list with the given type.
private Node newParameter(JSType type) {
Node paramNode = Node.newString(Token.NAME, "");
paramNode.setJSType(type);
root.addChildToBack(paramNode);
return paramNode;
}
public Node build() {
return root;
}
private boolean hasOptionalOrVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null &&
(lastChild.isOptionalArg() || lastChild.isVarArgs());
}
public boolean hasVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null && lastChild.isVarArgs();
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(JSType that) {
UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry);
UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
TypePair p = element.getTypesUnderInequality(that);
if (p.typeA != null) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public TypePair getTypesUnderShallowInequality(JSType that) {
UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry);
UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
TypePair p = element.getTypesUnderShallowInequality(that);
if (p.typeA != null) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseUnionType(this);
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this); // for circularly defined types.
boolean changed = false;
ImmutableList.Builder<JSType> resolvedTypes = ImmutableList.builder();
for (JSType alternate : alternates) {
JSType newAlternate = alternate.resolve(t, scope);
changed |= (alternate != newAlternate);
resolvedTypes.add(alternate);
}
if (changed) {
Collection<JSType> newAlternates = resolvedTypes.build();
Preconditions.checkState(
newAlternates.hashCode() == this.hashcode);
alternates = newAlternates;
}
return this;
}
@Override
public String toDebugHashCodeString() {
List<String> hashCodes = Lists.newArrayList();
for (JSType a : alternates) {
hashCodes.add(a.toDebugHashCodeString());
}
return "{(" + Joiner.on(",").join(hashCodes) + ")}";
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Builder.recordJavaDispatch()) {
parser.addWarning("msg.jsdoc.javadispatch",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case EXTENDS:
case IMPLEMENTS:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
boolean matchingRc = false;
if (token == JsDocToken.LC) {
token = next();
matchingRc = true;
}
if (token == JsDocToken.STRING) {
Node typeNode = parseAndRecordTypeNameNode(
token, lineno, charno, matchingRc);
lineno = stream.getLineno();
charno = stream.getCharno();
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && !matchingRc) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
type = createJSTypeExpression(typeNode);
if (annotation == Annotation.EXTENDS) {
if (!jsdocBuilder.recordBaseType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
} else {
Preconditions.checkState(
annotation == Annotation.IMPLEMENTS);
if (!jsdocBuilder.recordImplementedInterface(type)) {
parser.addWarning("msg.jsdoc.implements.duplicate",
lineno, charno);
}
}
token = next();
if (matchingRc) {
if (token != JsDocToken.RC) {
parser.addWarning("msg.jsdoc.missing.rc",
stream.getLineno(), stream.getCharno());
}
} else if (token != JsDocToken.EOL &&
token != JsDocToken.EOF && token != JsDocToken.EOC) {
parser.addWarning("msg.end.annotation.expected",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addWarning("msg.no.type.name", lineno, charno);
}
token = eatTokensUntilEOL(token);
continue retry;
case HIDDEN:
if (!jsdocBuilder.recordHiddenness()) {
parser.addWarning("msg.jsdoc.hidden",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case LENDS:
skipEOLs();
matchingRc = false;
if (match(JsDocToken.LC)) {
token = next();
matchingRc = true;
}
if (match(JsDocToken.STRING)) {
token = next();
if (!jsdocBuilder.recordLends(stream.getString())) {
parser.addWarning("msg.jsdoc.lends.incompatible",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addWarning("
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>TypeName(
templateTypeName)) {
parser.addWarning("msg.jsdoc.template.at.most.once",
stream.getLineno(), stream.getCharno());
}
token = templateInfo.token;
continue retry;
case VERSION:
ExtractionInfo versionInfo = extractSingleLineBlock();
String version = versionInfo.string;
if (version.length() == 0) {
parser.addWarning("msg.jsdoc.versionmissing",
stream.getLineno(), stream.getCharno());
} else {
if (!jsdocBuilder.recordVersion(version)) {
parser.addWarning("msg.jsdoc.extraversion",
stream.getLineno(), stream.getCharno());
}
}
token = versionInfo.token;
continue retry;
case DEFINE:
case RETURN:
case THIS:
case TYPE:
case TYPEDEF:
skipEOLs();
lineno = stream.getLineno();
charno = stream.getCharno();
token = next();
Node typeNode = parseAndRecordTypeNode(token, lineno, charno);
if (annotation == Annotation.THIS) {
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && token != JsDocToken.LC) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
}
type = createJSTypeExpression(typeNode);
if (type == null) {
// error reported during recursive descent
// recovering parsing
} else {
switch (annotation) {
case DEFINE:
if (!jsdocBuilder.recordDefineType(type)) {
parser.addWarning("msg.jsdoc.define",
lineno, charno);
}
break;
case RETURN:
if (!jsdocBuilder.recordReturnType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
break;
}
// *Update* the token to that after the type annotation.
token = current();
// Find the return's description (if applicable).
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo returnDescriptionInfo =
extractMultilineTextualBlock(token);
String returnDescription =
returnDescriptionInfo.string;
if (returnDescription.length() > 0) {
jsdocBuilder.recordReturnDescription(
returnDescription);
}
token = returnDescriptionInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case THIS:
if (!jsdocBuilder.recordThisType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
break;
case TYPE:
if (!jsdocBuilder.recordType(type)) {
parser.addWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
break;
case TYPEDEF:
if (!jsdocBuilder.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> it. Note that this method consumes input.
*
* @param token The current token.
* @param lineno The line of the type expression.
* @param startCharno The starting character position of the type expression.
* @param matchingLC Whether the type expression starts with a "{".
* @return The type expression found or null if none.
*/
private Node parseAndRecordTypeNameNode(JsDocToken token, int lineno,
int startCharno, boolean matchingLC) {
return parseAndRecordTypeNode(token, lineno, startCharno, matchingLC, true);
}
/**
* Looks for a type expression at the current token and if found,
* returns it. Note that this method consumes input.
*
* Parameter type expressions are special for two reasons:
* <ol>
* <li>They must begin with '{', to distinguish type names from param names.
* <li>They may end in '=', to denote optionality.
* </ol>
*
* @param token The current token.
* @return The type expression found or null if none.
*/
private Node parseAndRecordParamTypeNode(JsDocToken token) {
Preconditions.checkArgument(token == JsDocToken.LC);
int lineno = stream.getLineno();
int startCharno = stream.getCharno();
Node typeNode = parseParamTypeExpressionAnnotation(token);
int endCharno = stream.getCharno();
jsdocBuilder.markTypeNode(typeNode, lineno, startCharno, endCharno,
true);
return typeNode;
}
/**
* Looks for a parameter type expression at the current token and if found,
* returns it. Note that this method consumes input.
*
* @param token The current token.
* @param lineno The line of the type expression.
* @param startCharno The starting character position of the type expression.
* @param matchingLC Whether the type expression starts with a "{".
* @param onlyParseSimpleNames If true, only simple type names are parsed
* (via a call to parseTypeNameAnnotation instead of
* parseTypeExpressionAnnotation).
* @return The type expression found or null if none.
*/
private Node parseAndRecordTypeNode(JsDocToken token, int lineno,
int startCharno,
boolean matchingLC,
boolean onlyParseSimpleNames) {
Node typeNode = null;
if (onlyParseSimpleNames) {
typeNode = parseTypeNameAnnotation(token);
} else {
typeNode = parseTypeExpressionAnnotation(token);
}
if (typeNode != null && !matchingLC) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
int endCharno = stream.getCharno();
jsdocBuilder.markTypeNode(typeNode, lineno, startCharno, endCharno,
matchingLC);
return typeNode;
}
/**
* Converts a JSDoc token to its string representation.
*/
private String
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
private Node parseTypeExpressionAnnotation(JsDocToken token) {
if (token == JsDocToken.LC) {
skipEOLs();
Node typeNode = parseTopLevelTypeExpression(next());
if (typeNode != null) {
skipEOLs();
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
} else {
return parseTypeExpression(token);
}
}
/**
* ParamTypeExpressionAnnotation :=
* '{' OptionalParameterType '}' |
* '{' TopLevelTypeExpression '}' |
* '{' '...' TopLevelTypeExpression '}'
*
* OptionalParameterType :=
* TopLevelTypeExpression '='
*/
private Node parseParamTypeExpressionAnnotation(JsDocToken token) {
Preconditions.checkArgument(token == JsDocToken.LC);
skipEOLs();
boolean restArg = false;
token = next();
if (token == JsDocToken.ELLIPSIS) {
token = next();
if (token == JsDocToken.RC) {
// EMPTY represents the UNKNOWN type in the Type AST.
return wrapNode(Token.ELLIPSIS, new Node(Token.EMPTY));
}
restArg = true;
}
Node typeNode = parseTopLevelTypeExpression(token);
if (typeNode != null) {
skipEOLs();
if (restArg) {
typeNode = wrapNode(Token.ELLIPSIS, typeNode);
} else if (match(JsDocToken.EQUALS)) {
next();
skipEOLs();
typeNode = wrapNode(Token.EQUALS, typeNode);
}
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
}
/**
* TypeNameAnnotation := TypeName | '{' TypeName '}'
*/
private Node parseTypeNameAnnotation(JsDocToken token) {
if (token == JsDocToken.LC) {
skipEOLs();
Node typeNode = parseTypeName(next());
if (typeNode != null) {
skipEOLs();
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
} else {
return parseTypeName(token);
}
}
/**
* TopLevelTypeExpression := TypeExpression
* | TypeUnionList
*
* We made this rule up, for the sake of backwards compatibility.
*/
private Node parseTopLevelTypeExpression(JsDocToken token) {
Node typeExpr = parseTypeExpression(token);
if (typeExpr != null) {
// top-level unions are allowed
if (match(JsDocToken.PIPE)) {
next();
if (match(JsDocToken.PIPE)) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
// We support double pipes for backwards-compatibility.
next();
}
skipEOLs();
token = next();
return parseUnionTypeWithAlternate(token, typeExpr);
}
}
return typeExpr;
}
/**
* TypeExpressionList := TopLevelTypeExpression
* | TopLevelTypeExpression ',' TypeExpressionList
*/
private Node parseTypeExpressionList(JsDocToken token) {
Node typeExpr = parseTopLevelTypeExpression(token);
if (typeExpr == null) {
return null;
}
Node typeList = new Node(Token.BLOCK);
typeList.addChildToBack(typeExpr);
while (match(JsDocToken.COMMA)) {
next();
skipEOLs();
typeExpr = parseTopLevelTypeExpression(next());
if (typeExpr == null) {
return null;
}
typeList.addChildToBack(typeExpr);
}
return typeList;
}
/**
* TypeExpression := BasicTypeExpression
* | '?' BasicTypeExpression
* | '!' BasicTypeExpression
* | BasicTypeExpression '?'
* | BasicTypeExpression '!'
* | '?'
*/
private Node parseTypeExpression(JsDocToken token) {
if (token == JsDocToken.QMARK) {
// A QMARK could mean that a type is nullable, or that it's unknown.
// We use look-ahead 1 to determine whether it's unknown. Otherwise,
// we assume it means nullable. There are 5 cases:
// {?} - right curly
// {?=} - equals
// {function(?, number)} - comma
// {function(number, ?)} - right paren
// {function(): ?|number} - pipe
// I'm not a big fan of using look-ahead for this, but it makes
// the type language a lot nicer.
token = next();
if (token == JsDocToken.COMMA ||
token == JsDocToken.EQUALS ||
token == JsDocToken.RC ||
token == JsDocToken.RP ||
token == JsDocToken.PIPE) {
restoreLookAhead(token);
return newNode(Token.QMARK);
}
return wrapNode(Token.QMARK, parseBasicTypeExpression(token));
} else if (token == JsDocToken.BANG) {
return wrapNode(Token.BANG, parseBasicTypeExpression(next()));
} else {
Node basicTypeExpr = parseBasicTypeExpression(token);
if (basicTypeExpr != null) {
if (match(JsDocToken.QMARK)) {
next();
return wrapNode(Token.QMARK, basicTypeExpr);
} else if (match(JsDocToken.BANG)) {
next();
return wrapNode(Token.BANG, basicTypeExpr);
}
}
return basicTypeExpr;
}
}
/**
* BasicTypeExpression := '*' | 'null' | 'undefined' | TypeName
* | FunctionType | UnionType |
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> RecordType | ArrayType
*/
private Node parseBasicTypeExpression(JsDocToken token) {
if (token == JsDocToken.STAR) {
return newNode(Token.STAR);
} else if (token == JsDocToken.LB) {
skipEOLs();
return parseArrayType(next());
} else if (token == JsDocToken.LC) {
skipEOLs();
return parseRecordType(next());
} else if (token == JsDocToken.LP) {
skipEOLs();
return parseUnionType(next());
} else if (token == JsDocToken.STRING) {
String string = stream.getString();
if ("function".equals(string)) {
skipEOLs();
return parseFunctionType(next());
} else if ("null".equals(string) || "undefined".equals(string)) {
return newStringNode(string);
} else {
return parseTypeName(token);
}
}
return reportGenericTypeSyntaxWarning();
}
/**
* TypeName := NameExpression | NameExpression TypeApplication
* TypeApplication := '.<' TypeExpressionList '>'
* TypeExpressionList := TypeExpression // a white lie
*/
private Node parseTypeName(JsDocToken token) {
if (token != JsDocToken.STRING) {
return reportGenericTypeSyntaxWarning();
}
Node typeName = newStringNode(stream.getString());
if (match(JsDocToken.LT)) {
next();
skipEOLs();
Node memberType = parseTypeExpressionList(next());
if (memberType != null) {
typeName.addChildToFront(memberType);
skipEOLs();
if (!match(JsDocToken.GT)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.gt");
}
next();
}
}
return typeName;
}
/**
* FunctionType := 'function' FunctionSignatureType
* FunctionSignatureType :=
* TypeParameters '(' 'this' ':' TypeName, ParametersType ')' ResultType
*/
private Node parseFunctionType(JsDocToken token) {
// NOTE(nicksantos): We're not implementing generics at the moment, so
// just throw out TypeParameters.
if (token != JsDocToken.LP) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.lp");
}
Node functionType = newNode(Token.FUNCTION);
Node parameters = null;
skipEOLs();
if (!match(JsDocToken.RP)) {
token = next();
boolean hasParams = true;
if (token == JsDocToken.STRING && "this".equals(stream.getString())) {
if (match(JsDocToken.COLON)) {
next();
skipEOLs();
Node thisType = wrapNode(Token.THIS, parseTypeName(next()));
if (thisType == null) {
return null;
}
functionType.addChildToFront(thisType);
} else {
return reportTypeSyntaxWarning("msg.jsdoc.missing.colon");
}
if (match(Js
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>DocToken.COMMA)) {
next();
skipEOLs();
token = next();
} else {
hasParams = false;
}
}
if (hasParams) {
parameters = parseParametersType(token);
if (parameters == null) {
return null;
}
}
}
if (parameters != null) {
functionType.addChildToBack(parameters);
}
skipEOLs();
if (!match(JsDocToken.RP)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rp");
}
skipEOLs();
Node resultType = parseResultType(next());
if (resultType == null) {
return null;
} else {
functionType.addChildToBack(resultType);
}
return functionType;
}
/**
* ParametersType := RestParameterType | NonRestParametersType
* | NonRestParametersType ',' RestParameterType
* RestParameterType := '...' Identifier
* NonRestParametersType := ParameterType ',' NonRestParametersType
* | ParameterType
* | OptionalParametersType
* OptionalParametersType := OptionalParameterType
* | OptionalParameterType, OptionalParametersType
* OptionalParameterType := ParameterType=
* ParameterType := TypeExpression | Identifier ':' TypeExpression
*/
// NOTE(nicksantos): The official ES4 grammar forces optional and rest
// arguments to come after the required arguments. Our parser does not
// enforce this. Instead we allow them anywhere in the function at parse-time,
// and then warn about them during type resolution.
//
// In theory, it might be mathematically nicer to do the order-checking here.
// But in practice, the order-checking for structural functions is exactly
// the same as the order-checking for @param annotations. And the latter
// has to happen during type resolution. Rather than duplicate the
// order-checking in two places, we just do all of it in type resolution.
private Node parseParametersType(JsDocToken token) {
Node paramsType = newNode(Token.LP);
boolean isVarArgs = false;
Node paramType = null;
if (token != JsDocToken.RP) {
do {
if (paramType != null) {
// skip past the comma
next();
skipEOLs();
token = next();
}
if (token == JsDocToken.ELLIPSIS) {
// In the latest ES4 proposal, there are no type constraints allowed
// on variable arguments. We support the old syntax for backwards
// compatibility, but we should gradually tear it out.
skipEOLs();
if (match(JsDocToken.RP)) {
paramType = newNode(Token.ELLIPSIS);
} else {
skipEOLs();
if (!match(JsDocToken.LB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.lb");
}
next();
skipEOLs();
paramType = wrapNode(Token.ELLIPSIS, parseTypeExpression(next()));
skipEOLs();
if (!match
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(JsDocToken.RB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
}
skipEOLs();
next();
}
isVarArgs = true;
} else {
paramType = parseTypeExpression(token);
if (match(JsDocToken.EQUALS)) {
skipEOLs();
next();
paramType = wrapNode(Token.EQUALS, paramType);
}
}
if (paramType == null) {
return null;
}
paramsType.addChildToBack(paramType);
if (isVarArgs) {
break;
}
} while (match(JsDocToken.COMMA));
}
if (isVarArgs && match(JsDocToken.COMMA)) {
return reportTypeSyntaxWarning("msg.jsdoc.function.varargs");
}
// The right paren will be checked by parseFunctionType
return paramsType;
}
/**
* ResultType := <empty> | ':' void | ':' TypeExpression
*/
private Node parseResultType(JsDocToken token) {
skipEOLs();
if (!match(JsDocToken.COLON)) {
return newNode(Token.EMPTY);
}
token = next();
skipEOLs();
if (match(JsDocToken.STRING) && "void".equals(stream.getString())) {
next();
return newNode(Token.VOID);
} else {
return parseTypeExpression(next());
}
}
/**
* UnionType := '(' TypeUnionList ')'
* TypeUnionList := TypeExpression | TypeExpression '|' TypeUnionList
*
* We've removed the empty union type.
*/
private Node parseUnionType(JsDocToken token) {
return parseUnionTypeWithAlternate(token, null);
}
/**
* Create a new union type, with an alternate that has already been
* parsed. The alternate may be null.
*/
private Node parseUnionTypeWithAlternate(JsDocToken token, Node alternate) {
Node union = newNode(Token.PIPE);
if (alternate != null) {
union.addChildToBack(alternate);
}
Node expr = null;
do {
if (expr != null) {
skipEOLs();
token = next();
Preconditions.checkState(
token == JsDocToken.PIPE || token == JsDocToken.COMMA);
boolean isPipe = token == JsDocToken.PIPE;
if (isPipe && match(JsDocToken.PIPE)) {
// We support double pipes for backwards compatiblity.
next();
}
skipEOLs();
token = next();
}
expr = parseTypeExpression(token);
if (expr == null) {
return null;
}
union.addChildToBack(expr);
// We support commas for backwards compatiblity.
} while (match(JsDocToken.PIPE, JsDocToken.COMMA));
if (alternate == null) {
skipEOLs();
if (!match(JsDocToken.RP)) {
return reportTypeSyntaxWarning
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>("msg.jsdoc.missing.rp");
}
next();
}
return union;
}
/**
* ArrayType := '[' ElementTypeList ']'
* ElementTypeList := <empty> | TypeExpression | '...' TypeExpression
* | TypeExpression ',' ElementTypeList
*/
private Node parseArrayType(JsDocToken token) {
Node array = newNode(Token.LB);
Node arg = null;
boolean hasVarArgs = false;
do {
if (arg != null) {
next();
skipEOLs();
token = next();
}
if (token == JsDocToken.ELLIPSIS) {
arg = wrapNode(Token.ELLIPSIS, parseTypeExpression(next()));
hasVarArgs = true;
} else {
arg = parseTypeExpression(token);
}
if (arg == null) {
return null;
}
array.addChildToBack(arg);
if (hasVarArgs) {
break;
}
skipEOLs();
} while (match(JsDocToken.COMMA));
if (!match(JsDocToken.RB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
}
next();
return array;
}
/**
* RecordType := '{' FieldTypeList '}'
*/
private Node parseRecordType(JsDocToken token) {
Node recordType = newNode(Token.LC);
Node fieldTypeList = parseFieldTypeList(token);
if (fieldTypeList == null) {
return reportGenericTypeSyntaxWarning();
}
skipEOLs();
if (!match(JsDocToken.RC)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
}
next();
recordType.addChildToBack(fieldTypeList);
return recordType;
}
/**
* FieldTypeList := FieldType | FieldType ',' FieldTypeList
*/
private Node parseFieldTypeList(JsDocToken token) {
Node fieldTypeList = newNode(Token.LB);
do {
Node fieldType = parseFieldType(token);
if (fieldType == null) {
return null;
}
fieldTypeList.addChildToBack(fieldType);
skipEOLs();
if (!match(JsDocToken.COMMA)) {
break;
}
// Move to the comma token.
next();
// Move to the token passed the comma.
skipEOLs();
token = next();
} while (true);
return fieldTypeList;
}
/**
* FieldType := FieldName | FieldName ':' TypeExpression
*/
private Node parseFieldType(JsDocToken token) {
Node fieldName = parseFieldName(token);
if (fieldName == null) {
return null;
}
skipEOLs();
if (!match(JsDocToken.COLON)) {
return fieldName;
}
// Move to the colon.
next();
// Move to the token after the colon and parse
// the type expression.
skipEOLs();
Node typeExpression = parseTypeExpression(next());
if (typeExpression == null) {
return null;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> }
Node fieldType = newNode(Token.COLON);
fieldType.addChildToBack(fieldName);
fieldType.addChildToBack(typeExpression);
return fieldType;
}
/**
* FieldName := NameExpression | StringLiteral | NumberLiteral |
* ReservedIdentifier
*/
private Node parseFieldName(JsDocToken token) {
switch (token) {
case STRING:
String string = stream.getString();
return newStringNode(string);
default:
return null;
}
}
private Node wrapNode(int type, Node n) {
return n == null ? null :
new Node(type, n, stream.getLineno(), stream.getCharno());
}
private Node newNode(int type) {
return new Node(type, stream.getLineno(), stream.getCharno());
}
private Node newStringNode(String s) {
return Node.newString(s, stream.getLineno(), stream.getCharno());
}
private Node reportTypeSyntaxWarning(String warning) {
parser.addWarning(warning, stream.getLineno(), stream.getCharno());
return null;
}
private Node reportGenericTypeSyntaxWarning() {
return reportTypeSyntaxWarning("msg.jsdoc.type.syntax");
}
/**
* Eats tokens until {@link JsDocToken#EOL} included, and switches back the
* state to {@link State#SEARCHING_ANNOTATION}.
*/
private JsDocToken eatTokensUntilEOL() {
return eatTokensUntilEOL(next());
}
/**
* Eats tokens until {@link JsDocToken#EOL} included, and switches back the
* state to {@link State#SEARCHING_ANNOTATION}.
*/
private JsDocToken eatTokensUntilEOL(JsDocToken token) {
do {
if (token == JsDocToken.EOL || token == JsDocToken.EOC ||
token == JsDocToken.EOF) {
state = State.SEARCHING_ANNOTATION;
return token;
}
token = next();
} while (true);
}
/**
* Specific value indicating that the {@link #unreadToken} contains no token.
*/
private static final JsDocToken NO_UNREAD_TOKEN = null;
/**
* One token buffer.
*/
private JsDocToken unreadToken = NO_UNREAD_TOKEN;
/** Restores the lookahead token to the token stream */
private void restoreLookAhead(JsDocToken token) {
unreadToken = token;
}
/**
* Tests whether the next symbol of the token stream matches the specific
* token.
*/
private boolean match(JsDocToken token) {
unreadToken = next();
return unreadToken == token;
}
/**
* Tests that the next symbol of the token stream matches one of the specified
* tokens.
*/
private boolean match(JsDocToken token1, JsDocToken token2) {
unreadToken = next();
return unreadToken == token1 || unreadToken == token2;
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
/**
* The {@code StaticSlot} interface must be implemented by variables that can
* appear as members of a {@code StaticScope}.
*
* @param <T> The type of information stored about the slot
*/
public interface StaticSlot<T> {
/**
* Gets the name of the slot.
*/
String getName();
/**
* Returns the type information, if any, for this slot.
* @return The type or {@code null} if no type is declared for it.
*/
T getType();
/**
* Returns whether the type has been inferred (as opposed to declared).
*/
boolean isTypeInferred();
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> * graph coloring in {@link GraphColoring} to determine which two variables can
* be merge together safely.
*
*/
class CoalesceVariableNames extends AbstractPostOrderCallback implements
CompilerPass, ScopedCallback {
private final AbstractCompiler compiler;
private final Deque<GraphColoring<Var, Void>> colorings;
private final boolean usePseudoNames;
private static final Comparator<Var> coloringTieBreaker =
new Comparator<Var>() {
public int compare(Var v1, Var v2) {
return v1.index - v2.index;
}
};
/**
* @param usePseudoNames For debug purposes, when merging variable foo and bar
* to foo, rename both variable to foo_bar.
*/
CoalesceVariableNames(AbstractCompiler compiler, boolean usePseudoNames) {
Preconditions.checkState(!compiler.isNormalized());
this.compiler = compiler;
colorings = Lists.newLinkedList();
this.usePseudoNames = usePseudoNames;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void enterScope(NodeTraversal t) {
// TODO(user): We CAN do this in the global scope, just need to be
// careful when something is exported. Liveness uses bit-vector for live
// sets so I don't see compilation time will be a problem for running this
// pass in the global scope.
if (t.inGlobalScope()) {
return;
}
Scope scope = t.getScope();
ControlFlowGraph<Node> cfg = t.getControlFlowGraph();
LiveVariablesAnalysis liveness =
new LiveVariablesAnalysis(cfg, scope, compiler);
// If the function has exactly 2 params, mark them as escaped. This is
// a work-around for an IE bug where it throws an exception if you
// write to the parameters of the callback in a sort(). See:
// http://code.google.com/p/closure-compiler/issues/detail?id=58
if (scope.getRootNode().getFirstChild().getNext().getChildCount() == 2) {
liveness.markAllParametersEscaped();
}
liveness.analyze();
UndiGraph<Var, Void> interferenceGraph =
computeVariableNamesInterferenceGraph(
t, cfg, liveness.getEscapedLocals());
GraphColoring<Var, Void> coloring =
new GreedyGraphColoring<Var, Void>(interferenceGraph,
coloringTieBreaker);
coloring.color();
colorings.push(coloring);
}
@Override
public void exitScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return;
}
colorings.pop();
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (colorings.isEmpty() || !NodeUtil.isName(n) ||
NodeUtil.isFunction(parent
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> why
// that is but, for now, we will respect the dead functions and not play
// around with it.
if (!NodeUtil.isFunction(v.getParentNode())) {
interferenceGraph.createNode(v);
}
}
}
// Go through each variable and try to connect them.
for (Iterator<Var> i1 = scope.getVars(); i1.hasNext();) {
Var v1 = i1.next();
NEXT_VAR_PAIR:
for (Iterator<Var> i2 = scope.getVars(); i2.hasNext();) {
Var v2 = i2.next();
// Skip duplicate pairs.
if (v1.index >= v2.index) {
continue;
}
if (!interferenceGraph.hasNode(v1) ||
!interferenceGraph.hasNode(v2)) {
// Skip nodes that were not added. They are globals and escaped
// locals. Also avoid merging a variable with itself.
continue NEXT_VAR_PAIR;
}
if (v1.getParentNode().getType() == Token.LP &&
v2.getParentNode().getType() == Token.LP) {
interferenceGraph.connectIfNotFound(v1, null, v2);
continue NEXT_VAR_PAIR;
}
// Go through every CFG node in the program and look at
// this variable pair. If they are both live at the same
// time, add an edge between them and continue to the next pair.
NEXT_CROSS_CFG_NODE:
for (DiGraphNode<Node, Branch> cfgNode : cfg.getDirectedGraphNodes()) {
if (cfg.isImplicitReturn(cfgNode)) {
continue NEXT_CROSS_CFG_NODE;
}
FlowState<LiveVariableLattice> state = cfgNode.getAnnotation();
// Check the live states and add edge when possible.
if ((state.getIn().isLive(v1) && state.getIn().isLive(v2)) ||
(state.getOut().isLive(v1) && state.getOut().isLive(v2))) {
interferenceGraph.connectIfNotFound(v1, null, v2);
continue NEXT_VAR_PAIR;
}
}
// v1 and v2 might not have an edge between them! woohoo. there's
// one last sanity check that we have to do: we have to check
// if there's a collision *within* the cfg node.
NEXT_INTRA_CFG_NODE:
for (DiGraphNode<Node, Branch> cfgNode : cfg.getDirectedGraphNodes()) {
if (cfg.isImplicitReturn(cfgNode)) {
continue NEXT_INTRA_CFG_NODE;
}
FlowState<LiveVariableLattice> state = cfgNode.getAnnotation();
boolean v1OutLive = state.getOut().isLive(v1);
boolean v2OutLive = state.getOut().isLive(v2);
CombinedLiveRangeChecker checker = new CombinedLiveRangeChecker(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
new LiveRangeChecker(v1, v2OutLive ? null : v2),
new LiveRangeChecker(v2, v1OutLive ? null : v1));
NodeTraversal.traverse(
compiler,
cfgNode.getValue(),
checker);
if (checker.connectIfCrossed(interferenceGraph)) {
continue NEXT_VAR_PAIR;
}
}
}
}
return interferenceGraph;
}
/**
* A simple wrapper calls to call two AbstractCfgNodeTraversalCallback
* callback during the same traversal. Both traversals must have the same
* "shouldTraverse" conditions.
*/
private static class CombinedLiveRangeChecker
extends AbstractCfgNodeTraversalCallback {
private final LiveRangeChecker callback1;
private final LiveRangeChecker callback2;
CombinedLiveRangeChecker(
LiveRangeChecker callback1,
LiveRangeChecker callback2) {
this.callback1 = callback1;
this.callback2 = callback2;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (LiveRangeChecker.shouldVisit(n)) {
callback1.visit(t, n, parent);
callback2.visit(t, n, parent);
}
}
boolean connectIfCrossed(UndiGraph<Var, Void> interferenceGraph) {
if (callback1.crossed || callback2.crossed) {
Var v1 = callback1.getDef();
Var v2 = callback2.getDef();
interferenceGraph.connectIfNotFound(v1, null, v2);
return true;
}
return false;
}
}
/**
* Tries to remove variable declaration if the variable has been coalesced
* with another variable that has already been declared.
*/
private void removeVarDeclaration(Node name) {
Node var = name.getParent();
Node parent = var.getParent();
// Special case when we are in FOR-IN loop.
if (NodeUtil.isForIn(parent)) {
var.removeChild(name);
parent.replaceChild(var, name);
} else if (var.hasOneChild()) {
// The removal is easy when there is only one variable in the VAR node.
if (name.hasChildren()) {
Node value = name.removeFirstChild();
var.removeChild(name);
Node assign = new Node(Token.ASSIGN, name, value)
.copyInformationFrom(name);
// We don't need to wrapped it with EXPR node if it is within a FOR.
if (parent.getType() != Token.FOR) {
assign = NodeUtil.newExpr(assign);
}
parent.replaceChild(var, assign);
} else {
// In a FOR( ; ; ) node, we must replace it with an EMPTY or else it
// becomes a FOR-IN node.
NodeUtil.removeChild(parent, var);
}
} else {
if (!name.hasChildren()) {
var.removeChild(name);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> }
// We are going to leave duplicated declaration otherwise.
}
}
private static class LiveRangeChecker
extends AbstractCfgNodeTraversalCallback {
boolean defFound = false;
boolean crossed = false;
private final Var def;
private final Var use;
public LiveRangeChecker(Var def, Var use) {
this.def = def;
this.use = use;
}
Var getDef() {
return def;
}
/**
* @return Whether any LiveRangeChecker would be interested in the node.
*/
public static boolean shouldVisit(Node n) {
return (NodeUtil.isName(n)
|| (n.hasChildren() && NodeUtil.isName(n.getFirstChild())));
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!defFound && isAssignTo(def, n, parent)) {
defFound = true;
}
if (defFound && (use == null || isReadFrom(use, n))) {
crossed = true;
}
}
private static boolean isAssignTo(Var var, Node n, Node parent) {
if (NodeUtil.isName(n) && var.getName().equals(n.getString()) &&
parent != null) {
if (parent.getType() == Token.LP) {
// In a function declaration, the formal parameters are assigned.
return true;
} else if (NodeUtil.isVar(parent)) {
// If this is a VAR declaration, if the name node has a child, we are
// assigning to that name.
return n.hasChildren();
}
return false; // Definitely a read.
} else {
// Lastly, any assignmentOP is also an assign.
Node name = n.getFirstChild();
return name != null && NodeUtil.isName(name) &&
var.getName().equals(name.getString()) &&
NodeUtil.isAssignmentOp(n);
}
}
private static boolean isReadFrom(Var var, Node name) {
return name != null && NodeUtil.isName(name) &&
var.getName().equals(name.getString()) &&
!NodeUtil.isLhs(name, name.getParent());
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> *
* @param n1 Node one.
* @param n2 Node two.
* @return The list of edges between those two values in the graph.
*/
public abstract List<GraphEdge<N, E>> getEdges(N n1, N n2);
/**
* Checks whether the node exists in the graph ({@link #createNode(Object)}
* has been called with that value).
*
* @param n Node.
* @return <code>true</code> if it exist.
*/
public final boolean hasNode(N n) {
return getNode(n) != null;
}
/**
* Checks whether two nodes in the graph are connected.
*
* @param n1 Node 1.
* @param n2 Node 2.
* @return <code>true</code> if the two nodes are connected.
*/
public abstract boolean isConnected(N n1, N n2);
/**
* Checks whether two nodes in the graph are connected by the given
* edge type.
*
* @param n1 Node 1.
* @param e The edge type.
* @param n2 Node 2.
*/
public abstract boolean isConnected(N n1, E e, N n2);
/**
* Gets the node of the specified type, or throws an
* IllegalArgumentException.
*/
@SuppressWarnings("unchecked")
<T extends GraphNode<N, E>> T getNodeOrFail(N val) {
T node = (T) getNode(val);
if (node == null) {
throw new IllegalArgumentException(val + " does not exist in graph");
}
return node;
}
public final void clearNodeAnnotations() {
for (GraphNode<N, E> n : getNodes()) {
n.setAnnotation(null);
}
}
/** Makes each edge's annotation null. */
public final void clearEdgeAnnotations() {
for (GraphEdge<N, E> e : getEdges()) {
e.setAnnotation(null);
}
}
/**
* Pushes nodes' annotation values. Restored with
* {@link #popNodeAnnotations()}. Nodes' annotation values are cleared.
*/
public final void pushNodeAnnotations() {
if (nodeAnnotationStack == null) {
nodeAnnotationStack = Lists.newLinkedList();
}
pushAnnotations(nodeAnnotationStack, getNodes());
}
/**
* Restores nodes' annotation values to state before last
* {@link #pushNodeAnnotations()}.
*/
public final void popNodeAnnotations() {
Preconditions.checkNotNull(nodeAnnotationStack,
"Popping node annotations without pushing.");
popAnnotations(nodeAnnotationStack);
}
/**
* Pushes edges' annotation values. Restored with
* {@link #popEdgeAnnotations()}. Edges' annotation values are cleared.
*/
public final void pushEdgeAnnotations() {
if (edgeAnnotationStack == null) {
edgeAnnotationStack = Lists.newLinkedList();
}
pushAnnotations(edgeAnnotationStack, getEdges());
}
/**
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> * Restores edges' annotation values to state before last
* {@link #pushEdgeAnnotations()}.
*/
public final void popEdgeAnnotations() {
Preconditions.checkNotNull(edgeAnnotationStack,
"Popping edge annotations without pushing.");
popAnnotations(edgeAnnotationStack);
}
/**
* A generic edge.
*
* @param <N> Value type that the graph node stores.
* @param <E> Value type that the graph edge stores.
*/
public interface GraphEdge<N, E> extends Annotatable {
/**
* Retrieves the edge's value.
*
* @return The value.
*/
E getValue();
GraphNode<N, E> getNodeA();
GraphNode<N, E> getNodeB();
}
/**
* A simple implementation of SubGraph that calculates adjacency by iterating
* over a node's neighbors.
*/
class SimpleSubGraph<N, E> implements SubGraph<N, E> {
private Graph<N, E> graph;
private List<GraphNode<N, E>> nodes = Lists.newArrayList();
SimpleSubGraph(Graph<N, E> graph) {
this.graph = graph;
}
public boolean isIndependentOf(N value) {
GraphNode<N, E> node = graph.getNode(value);
for (GraphNode<N, E> n : nodes) {
if (graph.getNeighborNodes(n.getValue()).contains(node)) {
return false;
}
}
return true;
}
public void addNode(N value) {
nodes.add(graph.getNodeOrFail(value));
}
}
/**
* Pushes a new list on stack and stores nodes annotations in the new list.
* Clears objects' annotations as well.
*/
private static void pushAnnotations(
Deque<GraphAnnotationState> stack,
Collection<? extends Annotatable> haveAnnotations) {
stack.push(new GraphAnnotationState(haveAnnotations.size()));
for (Annotatable h : haveAnnotations) {
stack.peek().add(new AnnotationState(h, h.getAnnotation()));
h.setAnnotation(null);
}
}
/**
* Restores the node annotations on the top of stack and pops stack.
*/
private static void popAnnotations(Deque<GraphAnnotationState> stack) {
for (AnnotationState as : stack.pop()) {
as.first.setAnnotation(as.second);
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Predicate;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.GraphReachability;
import com.google.javascript.jscomp.graph.GraphReachability.EdgeTuple;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
/**
* Use {@link ControlFlowGraph} and {@link GraphReachability} to inform user
* about unreachable code.
*
*/
class CheckUnreachableCode implements ScopedCallback {
static final DiagnosticType UNREACHABLE_CODE = DiagnosticType.error(
"JSC_UNREACHABLE_CODE", "unreachable code");
private final AbstractCompiler compiler;
private final CheckLevel level;
CheckUnreachableCode(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.level = level;
}
@Override
public void enterScope(NodeTraversal t) {
initScope(t.getControlFlowGraph());
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
GraphNode<Node, Branch> gNode = t.getControlFlowGraph().getNode(n);
if (gNode != null && gNode.getAnnotation() != GraphReachability.REACHABLE) {
// Only report error when there are some line number informations.
// There are synthetic nodes with no line number informations, nodes
// introduce by other passes (although not likely since this pass should
// be executed early) or some rhino bug.
if (n.getLineno() != -1 &&
// Allow spurious semi-colons and spurious breaks.
n.getType() != Token.EMPTY && n.getType() != Token.BREAK) {
compiler.report(t.makeError(n, level, UNREACHABLE_CODE));
// From now on, we are going to assume the user fixed the error and not
// give more warning related to code section reachable from this node.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>, Node externsRoot, Node root) {
this.compiler = compiler;
this.externsRoot = externsRoot;
this.root = root;
}
/**
* Gets a list of the roots of the forest of the global names, where the
* roots are the top-level names.
*/
List<Name> getNameForest() {
if (!generated) {
process();
}
return globalNames;
}
/**
* Gets an index of all the global names, indexed by full qualified name
* (as in "a", "a.b.c", etc.).
*/
Map<String, Name> getNameIndex() {
if (!generated) {
process();
}
return nameMap;
}
/**
* If the client adds new nodes to the AST, scan these new nodes
* to see if they've added any references to the global namespace.
* @param scope The scope to scan.
* @param newNodes New nodes to check.
*/
void scanNewNodes(Scope scope, Set<Node> newNodes) {
NodeTraversal t = new NodeTraversal(compiler,
new BuildGlobalNamespace(new NodeFilter(newNodes)));
t.traverseAtScope(scope);
}
/**
* A filter that looks for qualified names that contain one of the nodes
* in the given set.
*/
private static class NodeFilter implements Predicate<Node> {
private final Set<Node> newNodes;
NodeFilter(Set<Node> newNodes) {
this.newNodes = newNodes;
}
public boolean apply(Node n) {
if (!n.isQualifiedName()) {
return false;
}
Node current;
for (current = n;
current.getType() == Token.GETPROP;
current = current.getFirstChild()) {
if (newNodes.contains(current)) {
return true;
}
}
return current.getType() == Token.NAME && newNodes.contains(current);
}
}
/**
* Builds the namespace lazily.
*/
private void process() {
if (externsRoot != null) {
inExterns = true;
NodeTraversal.traverse(compiler, externsRoot, new BuildGlobalNamespace());
}
inExterns = false;
NodeTraversal.traverse(compiler, root, new BuildGlobalNamespace());
generated = true;
}
/**
* Determines whether a name reference in a particular scope is a global name
* reference.
*
* @param name A variable or property name (e.g. "a" or "a.b.c.d")
* @param s The scope in which the name is referenced
* @return Whether the name reference is a global name reference
*/
private boolean isGlobalNameReference(String name, Scope s) {
String topVarName = getTopVarName(name);
return isGlobalVarReference(topVarName, s);
}
/**
* Gets the top variable name from a possibly namespaced name.
*
* @param name A variable or qualified
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> property name (e.g. "a" or "a.b.c.d")
* @return The top variable name (e.g. "a")
*/
private String getTopVarName(String name) {
int firstDotIndex = name.indexOf('.');
return firstDotIndex == -1 ? name : name.substring(0, firstDotIndex);
}
/**
* Determines whether a variable name reference in a particular scope is a
* global variable reference.
*
* @param name A variable name (e.g. "a")
* @param s The scope in which the name is referenced
* @return Whether the name reference is a global variable reference
*/
private boolean isGlobalVarReference(String name, Scope s) {
Scope.Var v = s.getVar(name);
if (v == null && externsScope != null) {
v = externsScope.getVar(name);
}
return v != null && !v.isLocal();
}
/**
* Gets whether a scope is the global scope.
*
* @param s A scope
* @return Whether the scope is the global scope
*/
private boolean isGlobalScope(Scope s) {
return s.getParent() == null;
}
// -------------------------------------------------------------------------
/**
* Builds a tree representation of the global namespace. Omits prototypes.
*/
private class BuildGlobalNamespace extends AbstractPostOrderCallback {
private final Predicate<Node> nodeFilter;
BuildGlobalNamespace() {
this(null);
}
/**
* Builds a global namepsace, but only visits nodes that match the
* given filter.
*/
BuildGlobalNamespace(Predicate<Node> nodeFilter) {
this.nodeFilter = nodeFilter;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (nodeFilter != null && !nodeFilter.apply(n)) {
return;
}
// If we are traversing the externs, then we save a pointer to the scope
// generated by them, so that we can do lookups in it later.
if (externsRoot != null && n == externsRoot) {
externsScope = t.getScope();
}
String name;
boolean isSet = false;
Name.Type type = Name.Type.OTHER;
boolean isPropAssign = false;
switch (n.getType()) {
case Token.GET:
case Token.SET:
case Token.STRING:
// This may be a key in an object literal declaration.
name = null;
if (parent != null && parent.getType() == Token.OBJECTLIT) {
name = getNameForObjLitKey(n);
}
if (name == null) return;
isSet = true;
switch (n.getType()) {
case Token.STRING:
type = getValueType(n.getFirstChild());
break;
case Token.GET:
type = Name.Type.GET;
break;
case Token.SET:
type = Name.Type.SET;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> break;
default:
throw new IllegalStateException("unexpected:" + n);
}
break;
case Token.NAME:
// This may be a variable get or set.
if (parent != null) {
switch (parent.getType()) {
case Token.VAR:
isSet = true;
Node rvalue = n.getFirstChild();
type = rvalue == null ? Name.Type.OTHER : getValueType(rvalue);
break;
case Token.ASSIGN:
if (parent.getFirstChild() == n) {
isSet = true;
type = getValueType(n.getNext());
}
break;
case Token.GETPROP:
return;
case Token.FUNCTION:
Node gramps = parent.getParent();
if (gramps == null ||
NodeUtil.isFunctionExpression(parent)) return;
isSet = true;
type = Name.Type.FUNCTION;
break;
}
}
name = n.getString();
break;
case Token.GETPROP:
// This may be a namespaced name get or set.
if (parent != null) {
switch (parent.getType()) {
case Token.ASSIGN:
if (parent.getFirstChild() == n) {
isSet = true;
type = getValueType(n.getNext());
isPropAssign = true;
}
break;
case Token.GETPROP:
return;
}
}
name = n.getQualifiedName();
if (name == null) return;
break;
default:
return;
}
// We are only interested in global names.
Scope scope = t.getScope();
if (!isGlobalNameReference(name, scope)) {
return;
}
if (isSet) {
if (isGlobalScope(scope)) {
handleSetFromGlobal(t, n, parent, name, isPropAssign, type);
} else {
handleSetFromLocal(t, n, parent, name);
}
} else {
handleGet(t, n, parent, name);
}
}
/**
* Gets the fully qualified name corresponding to an object literal key,
* as long as it and its prefix property names are valid JavaScript
* identifiers. The object literal may be nested inside of other object
* literals.
*
* For example, if called with node {@code n} representing "z" in any of
* the following expressions, the result would be "w.x.y.z":
* <code> var w = {x: {y: {z: 0}}}; </code>
* <code> w.x = {y: {z: 0}}; </code>
* <code> w.x.y = {'a': 0, 'z': 0}; </code>
*
* @param n A child of an OBJLIT node
* @return The global name, or null if {@code n} doesn't correspond to the
* key of an object literal that can be named
*/
String getNameForObjLitKey(Node n) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
Node parent = n.getParent();
Preconditions.checkState(parent.getType() == Token.OBJECTLIT);
Node gramps = parent.getParent();
if (gramps == null) {
return null;
}
Node greatGramps = gramps.getParent();
String name;
switch (gramps.getType()) {
case Token.NAME:
// VAR
// NAME (gramps)
// OBJLIT (parent)
// STRING (n)
if (greatGramps == null ||
greatGramps.getType() != Token.VAR) {
return null;
}
name = gramps.getString();
break;
case Token.ASSIGN:
// ASSIGN (gramps)
// NAME|GETPROP
// OBJLIT (parent)
// STRING (n)
Node lvalue = gramps.getFirstChild();
name = lvalue.getQualifiedName();
break;
case Token.STRING:
// OBJLIT
// STRING (gramps)
// OBJLIT (parent)
// STRING (n)
if (greatGramps != null &&
greatGramps.getType() == Token.OBJECTLIT) {
name = getNameForObjLitKey(gramps);
} else {
return null;
}
break;
default:
return null;
}
if (name != null) {
String key = n.getString();
if (TokenStream.isJSIdentifier(key)) {
return name + '.' + key;
}
}
return null;
}
/**
* Gets the type of a value or simple expression.
*
* @param n An rvalue in an assignment or variable declaration (not null)
* @return A {@link Name.Type}
*/
Name.Type getValueType(Node n) {
switch (n.getType()) {
case Token.OBJECTLIT:
return Name.Type.OBJECTLIT;
case Token.FUNCTION:
return Name.Type.FUNCTION;
case Token.OR:
// Recurse on the second value. If the first value were an object
// literal or function, then the OR would be meaningless and the
// second value would be dead code. Assume that if the second value
// is an object literal or function, then the first value will also
// evaluate to one when it doesn't evaluate to false.
return getValueType(n.getLastChild());
case Token.HOOK:
// The same line of reasoning used for the OR case applies here.
Node second = n.getFirstChild().getNext();
Name.Type t = getValueType(second);
if (t != Name.Type.OTHER) return t;
Node third = second.getNext();
return getValueType(third);
}
return Name.Type.OTHER;
}
/**
* Updates our respresentation of the global namespace to reflect an
* assignment to a global name in global scope.
*
* @param t The traversal
* @
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>param n The node currently being visited
* @param parent {@code n}'s parent
* @param name The global name (e.g. "a" or "a.b.c.d")
* @param isPropAssign Whether this set corresponds to a property
* assignment of the form <code>a.b.c = ...;</code>
* @param type The type of the value that the name is being assigned
*/
void handleSetFromGlobal(NodeTraversal t, Node n, Node parent, String name,
boolean isPropAssign, Name.Type type) {
if (maybeHandlePrototypePrefix(t, n, parent, name)) return;
Name nameObj = getOrCreateName(name);
nameObj.type = type;
Ref set = new Ref(t, n, Ref.Type.SET_FROM_GLOBAL);
nameObj.addRef(set);
if (isNestedAssign(parent)) {
// This assignment is both a set and a get that creates an alias.
Ref get = new Ref(t, n, Ref.Type.ALIASING_GET);
nameObj.addRef(get);
Ref.markTwins(set, get);
} else if (isConstructorOrEnumDeclaration(n, parent)) {
// Names with a @constructor or @enum annotation are always collapsed
nameObj.setIsClassOrEnum();
}
}
/**
* Determines whether a set operation is a constructor or enumeration
* declaration. The set operation may either be an assignment to a name,
* a variable declaration, or an object literal key mapping.
*
* @param n The node that represents the name being set
* @param parent Parent node of {@code n} (an ASSIGN, VAR, or OBJLIT node)
* @return Whether the set operation is either a constructor or enum
* declaration
*/
private boolean isConstructorOrEnumDeclaration(Node n, Node parent) {
// NOTE(nicksantos): This does not handle named constructors
// function a() {}
// For legacy reasons, we should not fix this, because we do not
// know who's depending on the current behavior.
JSDocInfo info;
int valueNodeType;
switch (parent.getType()) {
case Token.ASSIGN:
info = parent.getJSDocInfo();
valueNodeType = n.getNext().getType();
break;
case Token.VAR:
info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
Node valueNode = n.getFirstChild();
valueNodeType = valueNode != null ? valueNode.getType() : Token.VOID;
break;
default:
return false;
}
// Heed the annotations only if they're sensibly used.
return info != null &&
(info.isConstructor() && valueNodeType == Token.FUNCTION ||
info.hasEnumParameterType() && valueNodeType == Token.OBJECTLIT);
}
/**
* Updates our respresentation of the global namespace to reflect an
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
* assignment to a global name in a local scope.
*
* @param t The traversal
* @param n The node currently being visited
* @param parent {@code n}'s parent
* @param name The global name (e.g. "a" or "a.b.c.d")
*/
void handleSetFromLocal(NodeTraversal t, Node n, Node parent,
String name) {
if (maybeHandlePrototypePrefix(t, n, parent, name)) return;
Name node = getOrCreateName(name);
Ref set = new Ref(t, n, Ref.Type.SET_FROM_LOCAL);
node.addRef(set);
if (isNestedAssign(parent)) {
// This assignment is both a set and a get that creates an alias.
Ref get = new Ref(t, n, Ref.Type.ALIASING_GET);
node.addRef(get);
Ref.markTwins(set, get);
}
}
/**
* Updates our respresentation of the global namespace to reflect a read
* of a global name.
*
* @param t The traversal
* @param n The node currently being visited
* @param parent {@code n}'s parent
* @param name The global name (e.g. "a" or "a.b.c.d")
*/
void handleGet(NodeTraversal t, Node n, Node parent, String name) {
if (maybeHandlePrototypePrefix(t, n, parent, name)) return;
Ref.Type type = Ref.Type.DIRECT_GET;
if (parent != null) {
switch (parent.getType()) {
case Token.IF:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG:
break;
case Token.CALL:
type = n == parent.getFirstChild()
? Ref.Type.CALL_GET
: Ref.Type.ALIASING_GET;
break;
case Token.NEW:
type = n == parent.getFirstChild()
? Ref.Type.DIRECT_GET
: Ref.Type.ALIASING_GET;
break;
case Token.OR:
case Token.AND:
// This node is x or y in (x||y) or (x&&y). We only know that an
// alias is not getting created for this name if the result is used
// in a boolean context or assigned to the same name
// (e.g. var a = a || {}).
type = determineGetTypeForHookOrBooleanExpr(t, parent, name);
break;
case Token.HOOK:
if (n != parent.getFirstChild()) {
// This node is y or z in (x?y:z). We only know that an alias is
// not getting created for this name if the result is assigned to
// the same name (e.g. var a = a ? a : {}).
type = determineGetTypeForHookOr
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>BooleanExpr(t, parent, name);
}
break;
default:
type = Ref.Type.ALIASING_GET;
break;
}
}
handleGet(t, n, parent, name, type);
}
/**
* Determines whether the result of a hook (x?y:z) or boolean expression
* (x||y) or (x&&y) is assigned to a specific global name.
*
* @param t The traversal
* @param parent The parent of the current node in the traversal. This node
* should already be known to be a HOOK, AND, or OR node.
* @param name A name that is already known to be global in the current
* scope (e.g. "a" or "a.b.c.d")
* @return The expression's get type, either {@link Ref.Type#DIRECT_GET} or
* {@link Ref.Type#ALIASING_GET}
*/
Ref.Type determineGetTypeForHookOrBooleanExpr(
NodeTraversal t, Node parent, String name) {
Node prev = parent;
for (Node anc : parent.getAncestors()) {
switch (anc.getType()) {
case Token.EXPR_RESULT:
case Token.VAR:
case Token.IF:
case Token.WHILE:
case Token.FOR:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG:
return Ref.Type.DIRECT_GET;
case Token.HOOK:
if (anc.getFirstChild() == prev) {
return Ref.Type.DIRECT_GET;
}
break;
case Token.ASSIGN:
if (!name.equals(anc.getFirstChild().getQualifiedName())) {
return Ref.Type.ALIASING_GET;
}
break;
case Token.NAME: // a variable declaration
if (!name.equals(anc.getString())) {
return Ref.Type.ALIASING_GET;
}
break;
case Token.CALL:
if (anc.getFirstChild() != prev) {
return Ref.Type.ALIASING_GET;
}
break;
}
prev = anc;
}
return Ref.Type.ALIASING_GET;
}
/**
* Updates our respresentation of the global namespace to reflect a read
* of a global name.
*
* @param t The current node traversal
* @param n The node currently being visited
* @param parent {@code n}'s parent
* @param name The global name (e.g. "a" or "a.b.c.d")
* @param type The reference type
*/
void handleGet(NodeTraversal t, Node n, Node parent,
String name, Ref.Type type) {
Name node = getOrCreateName(name);
// No need to look up additional ancestors, since they won't be used.
node.addRef(new Ref(t
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>, n, type));
}
/**
* Updates our respresentation of the global namespace to reflect a read
* of a global name's longest prefix before the "prototype" property if the
* name includes the "prototype" property. Does nothing otherwise.
*
* @param t The current node traversal
* @param n The node currently being visited
* @param parent {@code n}'s parent
* @param name The global name (e.g. "a" or "a.b.c.d")
* @return Whether the name was handled
*/
boolean maybeHandlePrototypePrefix(NodeTraversal t, Node n, Node parent,
String name) {
// We use a string-based approach instead of inspecting the parse tree
// to avoid complexities with object literals, possibly nested, beneath
// assignments.
int numLevelsToRemove;
String prefix;
if (name.endsWith(".prototype")) {
numLevelsToRemove = 1;
prefix = name.substring(0, name.length() - 10);
} else {
int i = name.indexOf(".prototype.");
if (i == -1) {
return false;
}
prefix = name.substring(0, i);
numLevelsToRemove = 2;
i = name.indexOf('.', i + 11);
while (i >= 0) {
numLevelsToRemove++;
i = name.indexOf('.', i + 1);
}
}
if (parent != null && NodeUtil.isObjectLitKey(n, parent)) {
// Object literal keys have no prefix that's referenced directly per
// key, so we're done.
return true;
}
for (int i = 0; i < numLevelsToRemove; i++) {
parent = n;
n = n.getFirstChild();
}
handleGet(t, n, parent, prefix, Ref.Type.PROTOTYPE_GET);
return true;
}
/**
* Determines whether an assignment is nested (i.e. whether its return
* value is used).
*
* @param parent The parent of the current traversal node (not null)
* @return Whether it appears that the return value of the assignment is
* used
*/
boolean isNestedAssign(Node parent) {
return parent.getType() == Token.ASSIGN &&
!NodeUtil.isExpressionNode(parent.getParent());
}
/**
* Gets a {@link Name} instance for a global name. Creates it if necessary,
* as well as instances for any of its prefixes that are not yet defined.
*
* @param name A global name (e.g. "a", "a.b.c.d")
* @return The {@link Name} instance for {@code name}
*/
Name getOrCreateName(String name) {
Name node = nameMap.get(name);
if (node == null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
String parentName = name.substring(0, i);
Name parent
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
declaration = maybeNewDecl;
refs.remove(declaration);
break;
}
}
}
}
switch (ref.type) {
case SET_FROM_GLOBAL:
globalSets--;
break;
case SET_FROM_LOCAL:
localSets--;
break;
case PROTOTYPE_GET:
case DIRECT_GET:
totalGets--;
break;
case ALIASING_GET:
aliasingGets--;
totalGets--;
break;
case CALL_GET:
callGets--;
totalGets--;
break;
default:
throw new IllegalStateException();
}
}
}
void addRefInternal(Ref ref) {
if (refs == null) {
refs = new LinkedList<Ref>();
}
refs.add(ref);
}
boolean canEliminate() {
if (!canCollapseUnannotatedChildNames() || totalGets > 0) {
return false;
}
if (props != null) {
for (Name n : props) {
if (!n.canCollapse()) {
return false;
}
}
}
return true;
}
boolean canCollapse() {
return !inExterns && !isGetOrSetDefinition() && (isClassOrEnum ||
(parent == null || parent.canCollapseUnannotatedChildNames()) &&
(globalSets > 0 || localSets > 0));
}
boolean isGetOrSetDefinition() {
return this.type == Type.GET || this.type == Type.SET;
}
boolean canCollapseUnannotatedChildNames() {
if (type == Type.OTHER || isGetOrSetDefinition()
|| globalSets != 1 || localSets != 0) {
return false;
}
// Don't try to collapse if the one global set is a twin reference.
// We could theoretically handle this case in CollapseProperties, but
// it's probably not worth the effort.
Preconditions.checkNotNull(declaration);
if (declaration.getTwin() != null) {
return false;
}
if (isClassOrEnum) {
return true;
}
// If this is a key of an aliased object literal, then it will be aliased
// later. So we won't be able to collapse its properties.
if (parent != null && parent.shouldKeepKeys()) {
return false;
}
// If this is aliased, then its properties can't be collapsed either.
if (aliasingGets > 0) {
return false;
}
return (parent == null || parent.canCollapseUnannotatedChildNames());
}
/** Whether this is an object literal that needs to keep its keys. */
boolean shouldKeepKeys() {
return type == Type.OBJECTLIT && aliasingGets > 0;
}
boolean needsToBeStubbed() {
return globalSets == 0 && localSets > 0;
}
void setIsClassOrEnum() {
isClassOrEnum = true;
for (Name ancestor = parent; ancestor != null;
ancestor = ancestor.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>parent) {
ancestor.hasClassOrEnumDescendant = true;
}
}
/**
* Determines whether this name is a prefix of at least one class or enum
* name. Because classes and enums are always collapsed, the namespace will
* have different properties in compiled code than in uncompiled code.
*
* For example, if foo.bar.DomHelper is a class, then foo and foo.bar are
* considered namespaces.
*/
boolean isNamespace() {
return hasClassOrEnumDescendant && type == Type.OBJECTLIT;
}
/**
* Determines whether this is a simple name (as opposed to a qualified
* name).
*/
boolean isSimpleName() {
return parent == null;
}
@Override public String toString() {
return fullName() + " (" + type + "): globalSets=" + globalSets +
", localSets=" + localSets + ", totalGets=" + totalGets +
", aliasingGets=" + aliasingGets + ", callGets=" + callGets;
}
String fullName() {
return parent == null ? name : parent.fullName() + '.' + name;
}
/**
* Tries to get the doc info for a given declaration ref.
*/
private static JSDocInfo getDocInfoForDeclaration(Ref ref) {
if (ref.node != null) {
Node refParent = ref.node.getParent();
switch (refParent.getType()) {
case Token.FUNCTION:
case Token.ASSIGN:
return refParent.getJSDocInfo();
case Token.VAR:
return ref.node == refParent.getFirstChild() ?
refParent.getJSDocInfo() : ref.node.getJSDocInfo();
}
}
return null;
}
}
// -------------------------------------------------------------------------
/**
* A global name reference. Contains references to the relevant parse tree
* node and its ancestors that may be affected.
*/
static class Ref {
enum Type {
SET_FROM_GLOBAL,
SET_FROM_LOCAL,
PROTOTYPE_GET,
ALIASING_GET, // Prevents a name's properties from being collapsed
DIRECT_GET, // Prevents a name from being completely eliminated
CALL_GET, // Prevents a name from being collapsed if never set
}
Node node;
final Type type;
final String sourceName;
final Scope scope;
final JSModule module;
/**
* Certain types of references are actually double-refs. For example,
* var a = b = 0;
* counts as both a "set" of b and an "alias" of b.
*
* We create two Refs for this node, and mark them as twins of each other.
*/
private Ref twin = null;
/**
* Creates a reference at the current node.
*/
Ref(NodeTraversal t, Node name, Type type) {
this.node = name;
this.sourceName = t.getSourceName();
this.type = type;
this.scope
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> = t.getScope();
this.module = t.getModule();
}
private Ref(Ref original, Type type) {
this.node = original.node;
this.sourceName = original.sourceName;
this.type = type;
this.scope = original.scope;
this.module = original.module;
}
private Ref(Type type) {
this.type = type;
this.sourceName = "source";
this.scope = null;
this.module = null;
}
Ref getTwin() {
return twin;
}
boolean isSet() {
return type == Type.SET_FROM_GLOBAL || type == Type.SET_FROM_LOCAL;
}
static void markTwins(Ref a, Ref b) {
Preconditions.checkArgument(
(a.type == Type.ALIASING_GET || b.type == Type.ALIASING_GET) &&
(a.type == Type.SET_FROM_GLOBAL || a.type == Type.SET_FROM_LOCAL ||
b.type == Type.SET_FROM_GLOBAL || b.type == Type.SET_FROM_LOCAL));
a.twin = b;
b.twin = a;
}
/**
* Create a new ref that is the same as this one, but of
* a different class.
*/
Ref cloneAndReclassify(Type type) {
return new Ref(this, type);
}
static Ref createRefForTesting(Type type) {
return new Ref(type);
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> variable has more than one possible
// definition. We set the join of this to be BOTTOM regardless of what
// "b" might be.
resultMap.put(var, null);
continue;
}
Node aNode = aDef.node;
if (b.reachingDef.containsKey(var)) {
Definition bDef = b.reachingDef.get(var);
if (aDef.equals(bDef)) {
resultMap.put(var, aDef);
} else {
resultMap.put(var, null);
}
} else {
resultMap.put(var, aDef);
}
}
// Take the join of all variables that are not TOP in other but it is TOP
// in this.
for (Var var : b.reachingDef.keySet()) {
if (!a.reachingDef.containsKey(var)) {
resultMap.put(var, b.reachingDef.get(var));
}
}
return result;
}
}
@Override
boolean isForward() {
return true;
}
@Override
MustDef createEntryLattice() {
return new MustDef(jsScope.getVars());
}
@Override
MustDef createInitialEstimateLattice() {
return new MustDef();
}
@Override
MustDef flowThrough(Node n, MustDef input) {
// TODO(user): We are doing a straight copy from input to output. There
// might be some opportunities to cut down overhead.
MustDef output = new MustDef(input);
// TODO(user): This must know about ON_EX edges but it should handle
// it better than what we did in liveness. Because we are in a forward mode,
// we can used the branched forward analysis.
computeMustDef(n, n, output, false);
return output;
}
/**
* @param n The node in question.
* @param cfgNode The node to add
* @param conditional true if the definition is not always executed.
*/
private void computeMustDef(
Node n, Node cfgNode, MustDef output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.WHILE:
case Token.DO:
case Token.IF:
computeMustDef(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
return;
case Token.FOR:
if (!NodeUtil.isForIn(n)) {
computeMustDef(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
} else {
// for(x in y) {...}
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (NodeUtil.isVar(lhs)) {
lhs = lhs.getLastChild(); // for(var x in y) {...}
}
if (NodeUtil.isName(lhs)) {
addToDefIfLocal(lhs.getString(), cfg
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Node, rhs, output);
}
}
return;
case Token.AND:
case Token.OR:
computeMustDef(n.getFirstChild(), cfgNode, output, conditional);
computeMustDef(n.getLastChild(), cfgNode, output, true);
return;
case Token.HOOK:
computeMustDef(n.getFirstChild(), cfgNode, output, conditional);
computeMustDef(n.getFirstChild().getNext(), cfgNode, output, true);
computeMustDef(n.getLastChild(), cfgNode, output, true);
return;
case Token.VAR:
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.hasChildren()) {
computeMustDef(c.getFirstChild(), cfgNode, output, conditional);
addToDefIfLocal(c.getString(), conditional ? null : cfgNode,
c.getFirstChild(), output);
}
}
return;
default:
if (NodeUtil.isAssignmentOp(n) && NodeUtil.isName(n.getFirstChild())) {
Node name = n.getFirstChild();
computeMustDef(name.getNext(), cfgNode, output, conditional);
addToDefIfLocal(name.getString(), conditional ? null : cfgNode,
n.getLastChild(), output);
} else {
// DEC and INC actually defines the variable.
if (n.getType() == Token.DEC || n.getType() == Token.INC) {
Node target = n.getFirstChild();
if (NodeUtil.isName(target)) {
addToDefIfLocal(target.getString(),
conditional ? null : cfgNode, null, output);
return;
}
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
computeMustDef(c, cfgNode, output, conditional);
}
}
}
}
/**
* Set the variable lattice for the given name to the node value in the def
* lattice. Do nothing if the variable name is one of the escaped variable.
*
* @param node The CFG node where the definition should be record to.
* {@code null} if this is a conditional define.
*/
private void addToDefIfLocal( String name, @Nullable Node node,
@Nullable Node rValue, MustDef def) {
Var var = jsScope.getVar(name);
// var might be null because the variable might be defined in the extern
// that we might not traverse.
if (var == null || var.scope != jsScope) {
return;
}
for (Var other : def.reachingDef.keySet()) {
Definition otherDef = def.reachingDef.get(other);
if (otherDef == null) {
continue;
}
if (otherDef.depends.contains(var)) {
def.reachingDef.put(other, null);
}
}
if (!escaped.contains(var)) {
if (node == null) {
def.reachingDef.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>put(var, null);
} else {
Definition definition = new Definition(node);
if (rValue != null) {
computeDependence(definition, rValue);
}
def.reachingDef.put(var, definition);
}
}
}
/**
* Computes all the local variables that rValue reads from and store that
* in the def's depends set.
*/
private void computeDependence(final Definition def, Node rValue) {
NodeTraversal.traverse(compiler, rValue,
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isName(n) && jsScope.isDeclared(n.getString(), true)) {
def.depends.add(jsScope.getVar(n.getString()));
}
}
});
}
/**
* Gets the must reaching definition of a given node. The node must be one of
* the control flow graph nodes.
*
* @param name name of the variable. It can only be names of local variable
* that are not function parameters, escaped variables or variables
* declared in catch.
* @param useNode the location of the use where the definition reaches.
*/
Node getDef(String name, Node useNode) {
Preconditions.checkArgument(getCfg().hasNode(useNode));
GraphNode<Node, Branch> n = getCfg().getNode(useNode);
FlowState<MustDef> state = n.getAnnotation();
Definition def = state.getIn().reachingDef.get(jsScope.getVar(name));
if (def == null) {
return null;
} else {
return def.node;
}
}
boolean dependsOnOuterScopeVars(String name, Node useNode) {
Preconditions.checkArgument(getCfg().hasNode(useNode));
GraphNode<Node, Branch> n = getCfg().getNode(useNode);
FlowState<MustDef> state = n.getAnnotation();
Definition def = state.getIn().reachingDef.get(jsScope.getVar(name));
for (Var s : def.depends) {
if (s.scope != jsScope) {
return true;
}
}
return false;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.name = file.getName();
this.isExtern = isExtern;
}
/** Returns a name for this input. Must be unique across all inputs. */
@Override
public String getName() {
return name;
}
/** Gets the path relative to closure-base, if one is available. */
@Override
public String getPathRelativeToClosureBase() {
// TODO(nicksantos): Implement me.
throw new UnsupportedOperationException();
}
@Override
public Node getAstRoot(AbstractCompiler compiler) {
return ast.getAstRoot(compiler);
}
@Override
public void clearAst() {
ast.clearAst();
}
@Override
public SourceFile getSourceFile() {
return ast.getSourceFile();
}
@Override
public void setSourceFile(SourceFile file) {
ast.setSourceFile(file);
}
/** Returns the SourceAst object on which this input is based. */
public SourceAst getSourceAst() {
return ast;
}
/** Sets an error manager for routing error messages. */
public void setErrorManager(ErrorManager errorManager) {
this.errorManager = errorManager;
}
/** Sets an abstract compiler for doing parsing. */
public void setCompiler(AbstractCompiler compiler) {
this.compiler = compiler;
setErrorManager(compiler.getErrorManager());
}
/** Gets a list of types depended on by this input. */
@Override
public Collection<String> getRequires() {
Preconditions.checkNotNull(errorManager,
"Expected setErrorManager to be called first");
try {
regenerateDependencyInfoIfNecessary();
return Collections.<String>unmodifiableSet(requires);
} catch (IOException e) {
errorManager.report(CheckLevel.ERROR,
JSError.make(AbstractCompiler.READ_ERROR, getName()));
return ImmutableList.<String>of();
}
}
/** Gets a list of types provided by this input. */
@Override
public Collection<String> getProvides() {
Preconditions.checkNotNull(errorManager,
"Expected setErrorManager to be called first");
try {
regenerateDependencyInfoIfNecessary();
return Collections.<String>unmodifiableSet(provides);
} catch (IOException e) {
errorManager.report(CheckLevel.ERROR,
JSError.make(AbstractCompiler.READ_ERROR, getName()));
return ImmutableList.<String>of();
}
}
/**
* Regenerates the provides/requires if we need to do so.
*/
private void regenerateDependencyInfoIfNecessary() throws IOException {
// If the code is NOT a JsAst, then it was not originally JS code.
// Look at the Ast for dependency info.
if (!(ast instanceof JsAst)) {
Preconditions.checkNotNull(compiler,
"Expected setCompiler to be called first");
DepsFinder finder = new DepsFinder();
Node root = getAstRoot(compiler);
if (root == null) {
return;
}
finder.visitTree(getAstRoot(compiler));
// TODO(nicksantos|user): This
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> caching behavior is a bit
// odd, and only works if you assume the exact call flow that
// clients are currently using. In that flow, they call
// getProvides(), then remove the goog.provide calls from the
// AST, and then call getProvides() again.
//
// This won't work for any other call flow, or any sort of incremental
// compilation scheme. The API needs to be fixed so callers aren't
// doing weird things like this, and then we should get rid of the
// multiple-scan strategy.
provides.addAll(finder.provides);
requires.addAll(finder.requires);
} else {
// Otherwise, look at the source code.
if (!generatedDependencyInfoFromSource) {
// Note: it's ok to use getName() instead of
// getPathRelativeToClosureBase() here because we're not using
// this to generate deps files. (We're only using it for
// symbol dependencies.)
DependencyInfo info = (new JsFileParser(errorManager)).parseFile(
getName(), getName(), getCode());
provides.addAll(info.getProvides());
requires.addAll(info.getRequires());
generatedDependencyInfoFromSource = true;
}
}
}
private static class DepsFinder {
private final List<String> provides = Lists.newArrayList();
private final List<String> requires = Lists.newArrayList();
private final CodingConvention codingConvention =
new ClosureCodingConvention();
void visitTree(Node n) {
visitSubtree(n, null);
}
void visitSubtree(Node n, Node parent) {
if (n.getType() == Token.CALL) {
String require =
codingConvention.extractClassNameIfRequire(n, parent);
if (require != null) {
requires.add(require);
}
String provide =
codingConvention.extractClassNameIfProvide(n, parent);
if (provide != null) {
provides.add(provide);
}
return;
} else if (parent != null &&
parent.getType() != Token.EXPR_RESULT &&
parent.getType() != Token.SCRIPT) {
return;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
visitSubtree(child, n);
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
return getSourceFile().getRegion(lineNumber);
}
public String getCode() throws IOException {
return getSourceFile().getCode();
}
/** Returns the module to which the input belongs. */
public JSModule getModule() {
return module;
}
/** Sets the module to which the input belongs. */
public void setModule(JSModule module) {
// An input may only belong to one module.
Preconditions.checkArgument(
module == null || this.module == null || this.module == module);
this.module = module;
}
public boolean isExtern() {
return isExtern;
}
void setIsExtern(boolean isExtern) {
this.isExtern = isExtern;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> unknown
* @param charno Column number within line, or -1 for whole line.
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, int lineno, int charno,
CheckLevel level, DiagnosticType type, String... arguments) {
return new JSError(
sourceName, null, lineno, charno, type, level, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, type, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n, CheckLevel level,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, n.getLineno(), n.getCharno(), type, level,
arguments);
}
//
// JSError constructors
//
/**
* Creates a JSError at a CheckLevel for a source file location.
* Private to avoid any entanglement with code outside of the compiler.
*/
private JSError(
String sourceName, @Nullable Node node, int lineno, int charno,
DiagnosticType type, CheckLevel level, String... arguments) {
this.type = type;
this.node = node;
this.description = type.format.format(arguments);
this.lineNumber = lineno;
this.charno = charno;
this.sourceName = sourceName;
this.level = level == null ? type.level : level;
}
/**
* Creates a JSError for a source file location. Private to avoid
* any entanglement with code outside of the compiler.
*/
private JSError(String sourceName, @Nullable Node node,
DiagnosticType type, String... arguments) {
this(sourceName,
node,
(node != null) ? node.getLineno() : -1,
(node != null) ? node.getCharno() : -1,
type, null, arguments);
}
public DiagnosticType getType() {
return type;
}
/**
* Format a message at the given level.
*
* @return the formatted message or {@code null}
*/
public String format(CheckLevel level, MessageFormatter formatter) {
switch (level) {
case ERROR:
return formatter.formatError(this);
case
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>S_THROW",
"The body of a goog.scope function cannot use 'throw'.");
static final DiagnosticType GOOG_SCOPE_ALIAS_REDEFINED = DiagnosticType.error(
"JSC_GOOG_SCOPE_ALIAS_REDEFINED",
"The alias {0} is assigned a value more than once.");
static final DiagnosticType GOOG_SCOPE_NON_ALIAS_LOCAL = DiagnosticType.error(
"JSC_GOOG_SCOPE_NON_ALIAS_LOCAL",
"The local variable {0} is in a goog.scope and is not an alias.");
ScopedAliases(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
Traversal traversal = new Traversal();
NodeTraversal.traverse(compiler, root, traversal);
if (!traversal.hasErrors()) {
// Apply the aliases.
for (AliasUsage aliasUsage : traversal.getAliasUsages()) {
aliasUsage.applyAlias();
}
// Remove the alias definitions.
for (Node aliasDefinition : traversal.getAliasDefinitions()) {
if (aliasDefinition.getParent().getType() == Token.VAR &&
aliasDefinition.getParent().hasOneChild()) {
aliasDefinition.getParent().detachFromParent();
} else {
aliasDefinition.detachFromParent();
}
}
// Collapse the scopes.
for (Node scopeCall : traversal.getScopeCalls()) {
Node expressionWithScopeCall = scopeCall.getParent();
Node scopeClosureBlock = scopeCall.getLastChild().getLastChild();
scopeClosureBlock.detachFromParent();
expressionWithScopeCall.getParent().replaceChild(
expressionWithScopeCall,
scopeClosureBlock);
NodeUtil.tryMergeBlock(scopeClosureBlock);
}
if (traversal.getAliasUsages().size() > 0 ||
traversal.getAliasDefinitions().size() > 0 ||
traversal.getScopeCalls().size() > 0) {
compiler.reportCodeChange();
}
}
}
private interface AliasUsage {
public void applyAlias();
}
private class AliasedNode implements AliasUsage {
private final Node aliasReference;
private final Node aliasDefinition;
AliasedNode(Node aliasReference, Node aliasDefinition) {
this.aliasReference = aliasReference;
this.aliasDefinition = aliasDefinition;
}
public void applyAlias() {
aliasReference.getParent().replaceChild(
aliasReference, aliasDefinition.cloneTree());
}
}
private class AliasedTypeNode implements AliasUsage {
private final Node aliasReference;
private final String correctedType;
AliasedTypeNode(Node aliasReference, String correctedType) {
this.aliasReference = aliasReference;
this.correctedType = correctedType;
}
public void applyAlias() {
aliasReference.setString(correctedType);
}
}
private class Traversal implements NodeTraversal.ScopedCallback {
// The job of this class is to collect these three data sets.
private List<Node> aliasDefinitions = Lists.newArrayList();
private List<
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Node> scopeCalls = Lists.newArrayList();
private List<AliasUsage> aliasUsages = Lists.newArrayList();
// This map is temporary and cleared for each scope.
private Map<String, Var> aliases = Maps.newHashMap();
private boolean hasErrors = false;
List<Node> getAliasDefinitions() {
return aliasDefinitions;
}
private List<AliasUsage> getAliasUsages() {
return aliasUsages;
}
List<Node> getScopeCalls() {
return scopeCalls;
}
boolean hasErrors() {
return hasErrors;
}
private boolean isCallToScopeMethod(Node n) {
return n.getType() == Token.CALL &&
SCOPING_METHOD_NAME.equals(n.getFirstChild().getQualifiedName());
}
@Override
public void enterScope(NodeTraversal t) {
}
@Override
public void exitScope(NodeTraversal t) {
if (t.getScopeDepth() == 2) {
aliases.clear();
}
}
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION && t.inGlobalScope()) {
// Do not traverse in to functions except for goog.scope functions.
if (parent == null || !isCallToScopeMethod(parent)) {
return false;
}
}
return true;
}
private void report(NodeTraversal t, Node n, DiagnosticType error,
String... arguments) {
compiler.report(t.makeError(n, error, arguments));
hasErrors = true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (isCallToScopeMethod(n)) {
if (!NodeUtil.isExpressionNode(parent)) {
report(t, n, GOOG_SCOPE_USED_IMPROPERLY);
}
if (n.getChildCount() != 2) {
// The goog.scope call should have exactly 1 parameter. The first
// child is the "goog.scope" and the second should be the parameter.
report(t, n, GOOG_SCOPE_HAS_BAD_PARAMETERS);
} else {
Node anonymousFnNode = n.getChildAtIndex(1);
if (!NodeUtil.isFunction(anonymousFnNode) ||
NodeUtil.getFunctionName(anonymousFnNode) != null ||
NodeUtil.getFnParameters(anonymousFnNode).hasChildren()) {
report(t, anonymousFnNode, GOOG_SCOPE_HAS_BAD_PARAMETERS);
} else {
scopeCalls.add(n);
}
}
}
if (t.getScopeDepth() == 2) {
int type = n.getType();
if (type == Token.NAME && parent.getType() == Token.VAR) {
if (n.hasChildren() && n.getFirstChild().isQualifiedName()) {
aliases.put(n.getString(), t.getScope().getVar(n.getString()));
aliasDefinitions.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>add(n);
// If we found an alias, we are done.
return;
} else {
// TODO(robbyw): Support using locals for private variables.
report(t, n, GOOG_SCOPE_NON_ALIAS_LOCAL, n.getString());
}
}
if (type == Token.NAME && NodeUtil.isAssignmentOp(parent) &&
n == parent.getFirstChild()) {
report(t, n, GOOG_SCOPE_ALIAS_REDEFINED, n.getString());
}
if (type == Token.RETURN) {
report(t, n, GOOG_SCOPE_USES_RETURN);
} else if (type == Token.THIS) {
report(t, n, GOOG_SCOPE_REFERENCES_THIS);
} else if (type == Token.THROW) {
report(t, n, GOOG_SCOPE_USES_THROW);
}
}
if (t.getScopeDepth() >= 2) {
if (n.getType() == Token.NAME) {
String name = n.getString();
Var aliasVar = aliases.get(name);
// Check if this name points to an alias.
if (aliasVar != null &&
t.getScope().getVar(name) == aliasVar) {
// Note, to support the transitive case, it's important we don't
// clone aliasedNode here. For example,
// var g = goog; var d = g.dom; d.createElement('DIV');
// The node in aliasedNode (which is "g") will be replaced in the
// changes pass above with "goog". If we cloned here, we'd end up
// with <code>g.dom.createElement('DIV')</code>.
Node aliasedNode = aliasVar.getInitialValue();
aliasUsages.add(new AliasedNode(n, aliasedNode));
}
}
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
for (Node node : info.getTypeNodes()) {
fixTypeNode(node);
}
}
// TODO(robbyw): Error for goog.scope not at root.
}
}
private void fixTypeNode(Node typeNode) {
if (typeNode.getType() == Token.STRING) {
String name = typeNode.getString();
int endIndex = name.indexOf('.');
if (endIndex == -1) {
endIndex = name.length();
}
String baseName = name.substring(0, endIndex);
Var aliasVar = aliases.get(baseName);
if (aliasVar != null) {
Node aliasedNode = aliasVar.getInitialValue();
aliasUsages.add(new AliasedTypeNode(typeNode,
aliasedNode.getQualifiedName() + name.substring(endIndex)));
}
}
for (Node child = typeNode.getFirstChild(); child != null;
child = child.getNext()) {
fixTypeNode(child);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
/** Warnings guards for each suppressable warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressable groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressableGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressableGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
// We only care about function annotations at the FUNCTION and SCRIPT
// level. Otherwise, the @suppress annotation has an implicit
// dependency on the exact structure of our AST, and that seems like
// a bad idea.
if (type == Token.FUNCTION) {
info = NodeUtil.getFunctionInfo(current);
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
}
if (info != null) {
for (String suppressor : info.getSuppressions()) {
WarningsGuard guard = suppressors.get(suppressor);
// Some @suppress tags are for other tools, and
// may not have a warnings guard.
if (guard != null) {
CheckLevel newLevel = guard.level(error);
if (newLevel != null) {
return newLevel;
}
}
}
}
}
}
return null;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> functionNode);
abstract T processIfStatement(IfStatement statementNode);
abstract T processInfixExpression(InfixExpression exprNode);
abstract T processKeywordLiteral(KeywordLiteral literalNode);
abstract T processLabel(Label labelNode);
abstract T processLabeledStatement(LabeledStatement statementNode);
abstract T processName(Name nameNode);
abstract T processNewExpression(NewExpression exprNode);
abstract T processNumberLiteral(NumberLiteral literalNode);
abstract T processObjectLiteral(ObjectLiteral literalNode);
abstract T processObjectProperty(ObjectProperty propertyNode);
abstract T processParenthesizedExpression(ParenthesizedExpression exprNode);
abstract T processPropertyGet(PropertyGet getNode);
abstract T processRegExpLiteral(RegExpLiteral literalNode);
abstract T processReturnStatement(ReturnStatement statementNode);
abstract T processScope(Scope scopeNode);
abstract T processStringLiteral(StringLiteral literalNode);
abstract T processSwitchCase(SwitchCase caseNode);
abstract T processSwitchStatement(SwitchStatement statementNode);
abstract T processThrowStatement(ThrowStatement statementNode);
abstract T processTryStatement(TryStatement statementNode);
abstract T processUnaryExpression(UnaryExpression exprNode);
abstract T processVariableDeclaration(VariableDeclaration declarationNode);
abstract T processVariableInitializer(VariableInitializer initializerNode);
abstract T processWhileLoop(WhileLoop loopNode);
abstract T processWithStatement(WithStatement statementNode);
abstract T processIllegalToken(AstNode node);
public T process(AstNode node) {
switch (node.getType()) {
case Token.ADD:
case Token.AND:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.OR:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return processInfixExpression((InfixExpression) node);
case Token.ARRAYLIT:
return processArrayLiteral((ArrayLiteral) node);
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_DIV:
case Token.ASSIGN_LSH:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_RSH:
case Token.ASSIGN_SUB:
case Token.ASSIGN_URSH:
return processAssignment((Assignment) node);
case Token.BITNOT:
case Token.DEC:
case Token.DELPROP:
case Token.INC:
case Token.NEG:
case Token.NOT:
case Token.POS:
case Token.TYPEOF:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
case Token.VOID:
return processUnaryExpression((UnaryExpression) node);
case Token.BLOCK:
if (node instanceof Block) {
return processBlock((Block) node);
} else if (node instanceof Scope) {
return processScope((Scope) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.BREAK:
return processBreakStatement((BreakStatement) node);
case Token.CALL:
return processFunctionCall((FunctionCall) node);
case Token.CASE:
case Token.DEFAULT:
return processSwitchCase((SwitchCase) node);
case Token.CATCH:
case Token.FINALLY:
return processCatchClause((CatchClause) node);
case Token.COLON:
return processObjectProperty((ObjectProperty) node);
case Token.CONTINUE:
return processContinueStatement((ContinueStatement) node);
case Token.DO:
return processDoLoop((DoLoop) node);
case Token.EMPTY:
return processEmptyExpression((EmptyExpression) node);
case Token.EXPR_RESULT:
case Token.EXPR_VOID:
if (node instanceof ExpressionStatement) {
return processExpressionStatement((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
return processLabeledStatement((LabeledStatement) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.DEBUGGER:
case Token.FALSE:
case Token.NULL:
case Token.THIS:
case Token.TRUE:
return processKeywordLiteral((KeywordLiteral) node);
case Token.FOR:
if (node instanceof ForInLoop) {
return processForInLoop((ForInLoop) node);
} else if (node instanceof ForLoop) {
return processForLoop((ForLoop) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.FUNCTION:
return processFunctionNode((FunctionNode) node);
case Token.GETELEM:
return processElementGet((ElementGet) node);
case Token.GETPROP:
return processPropertyGet((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name) node);
case Token.NEW:
return processNewExpression((NewExpression) node);
case Token.NUMBER:
return processNumberLiteral((NumberLiteral) node);
case Token.OBJECTLIT:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> return processObjectLiteral((ObjectLiteral) node);
case Token.REGEXP:
return processRegExpLiteral((RegExpLiteral) node);
case Token.RETURN:
return processReturnStatement((ReturnStatement) node);
case Token.SCRIPT:
return processAstRoot((AstRoot) node);
case Token.STRING:
return processStringLiteral((StringLiteral) node);
case Token.SWITCH:
return processSwitchStatement((SwitchStatement) node);
case Token.THROW:
return processThrowStatement((ThrowStatement) node);
case Token.TRY:
return processTryStatement((TryStatement) node);
case Token.VAR:
if (node instanceof VariableDeclaration) {
return processVariableDeclaration((VariableDeclaration) node);
} else if (node instanceof VariableInitializer) {
return processVariableInitializer((VariableInitializer) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.WHILE:
return processWhileLoop((WhileLoop) node);
case Token.WITH:
return processWithStatement((WithStatement) node);
}
return processIllegalToken(node);
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* NodeUtil contains utilities that get properties from the Node object.
*
*/
public final class NodeUtil {
final static String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty";
// TODO(user): Eliminate this class and make all of the static methods
// instance methods of com.google.javascript.rhino.Node.
/** the set of builtin constructors that don't have side effects. */
private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS =
new HashSet<String>(Arrays.asList(
"Array",
"Date",
"Error",
"Object",
"RegExp",
"XMLHttpRequest"));
// Utility class; do not instantiate.
private NodeUtil() {}
/**
* Gets the boolean value of a node that represents a expression. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getExpressionBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the RHS.
return getExpressionBooleanValue(n.getLastChild());
case Token.NOT:
TernaryValue value = getExpressionBooleanValue(n.getLastChild
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>());
return value.not();
case Token.AND: {
TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild());
TernaryValue rhs = getExpressionBooleanValue(n.getLastChild());
return lhs.and(rhs);
}
case Token.OR: {
TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild());
TernaryValue rhs = getExpressionBooleanValue(n.getLastChild());
return lhs.or(rhs);
}
case Token.HOOK: {
TernaryValue trueValue = getExpressionBooleanValue(
n.getFirstChild().getNext());
TernaryValue falseValue = getExpressionBooleanValue(n.getLastChild());
if (trueValue.equals(falseValue)) {
return trueValue;
} else {
return TernaryValue.UNKNOWN;
}
}
default:
return getBooleanValue(n);
}
}
/**
* Gets the boolean value of a node that represents a literal. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
*/
static TernaryValue getBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return TernaryValue.forBoolean(n.getString().length() > 0);
case Token.NUMBER:
return TernaryValue.forBoolean(n.getDouble() != 0);
case Token.NULL:
case Token.FALSE:
case Token.VOID:
return TernaryValue.FALSE;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return TernaryValue.FALSE;
} else if ("Infinity".equals(name)) {
return TernaryValue.TRUE;
}
break;
case Token.TRUE:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
return TernaryValue.TRUE;
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): Convert constant array, object, and regex literals as well.
switch (n.getType()) {
case Token.STRING:
return n.getString();
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.NUMBER:
double value = n.getDouble();
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> return Long.toString(longValue);
} else {
return Double.toString(n.getDouble());
}
case Token.FALSE:
case Token.TRUE:
case Token.NULL:
return Node.tokenToName(n.getType());
case Token.VOID:
return "undefined";
}
return null;
}
/**
* Gets the value of a node as a Number, or null if it cannot be converted.
* When it returns a non-null Double, this method effectively emulates the
* <code>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
return Double.NaN;
case Token.NAME:
String name = n.getString();
if (name.equals("undefined")) {
return Double.NaN;
}
if (name.equals("NaN")) {
return Double.NaN;
}
if (name.equals("Infinity")) {
return Double.POSITIVE_INFINITY;
}
return null;
}
return null;
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Node parent = n.getParent();
String name = n.getFirstChild().getString();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getString();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
return name != null && name.length() != 0 ? name : null;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getNearestFunctionName(Node n) {
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.STRING:
// Return the name of the literal's key.
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code>
* If it is evaluated in a different scope, then it
* captures a different variable. Even if the function did not read
* any captured vairables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.ARRAYLIT:
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!is
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>LiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Uniary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (block.getType() != Token.BLOCK) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (n.getType() != Token.EMPTY) {
return false;
}
}
return true;
}
static boolean isSimpleOperator(Node n) {
return isSimpleOperatorType(n.getType());
}
/**
* A "simple" operator is one whose children are expressions,
* has no direct side-effects (unlike '+='), and has no
* conditional aspects (unlike '||').
*/
static boolean isSimpleOperatorType
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(int type) {
switch (type) {
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GETELEM:
case Token.GETPROP:
case Token.GT:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.NOT:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.TYPEOF:
case Token.VOID:
case Token.POS:
case Token.NEG:
case Token.URSH:
return true;
default:
return false;
}
}
/**
* Creates an EXPR_RESULT.
*
* @param child The expression itself.
* @return Newly created EXPR node with the child as subexpression.
*/
public static Node newExpr(Node child) {
Node expr = new Node(Token.EXPR_RESULT, child)
.copyInformationFrom(child);
return expr;
}
/**
* Returns true if the node may create new mutable state, or change existing
* state.
*
* @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a>
*/
static boolean mayEffectMutableState(Node n) {
return mayEffectMutableState(n, null);
}
static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, true, compiler);
}
/**
* Returns true if the node which may have side effects when executed.
*/
static boolean mayHaveSideEffects(Node n) {
return mayHaveSideEffects(n, null);
}
static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, false, compiler);
}
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.LP:
case Token.NUMBER:
case Token.OR:
case Token.THIS
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
if (checkForNewObjects) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(
c.getFirstChild(), checkForNewObjects, compiler)) {
return true;
}
}
return false;
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null) {
return true;
}
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return checkForNewObjects || !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperatorType(n.getType())) {
break;
}
if (isAssignmentOp(n)) {
Node assignTarget = n.getFirstChild();
if (isName(assignTarget)) {
return true;
}
// Assignments will have side effects if
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects, compiler) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects, compiler)) {
return true;
}
if (isGet(assignTarget)) {
// If the object being assigned to is a local object, don't
// consider this a side-effect as it can't be referenced
// elsewhere. Don't do this recursively as the property might
// be an alias of another object, unlike a literal below.
Node current = assignTarget.getFirstChild();
if (evaluatesToLocalValue(current)) {
return false;
}
// A literal value as defined by "isLiteralValue
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>" is guaranteed
// not to be an alias, or any components which are aliases of
// other objects.
// If the root object is a literal don't consider this a
// side-effect.
while (isGet(current)) {
current = current.getFirstChild();
}
return !isLiteralValue(current, true);
} else {
// TODO(johnlenz): remove this code and make this an exception. This
// is here only for legacy reasons, the AST is not valid but
// preserve existing behavior.
return !isLiteralValue(assignTarget, true);
}
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) {
return true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - construtor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
return constructorCallHasSideEffects(callNode, null);
}
static boolean constructorCallHasSideEffects(
Node callNode, AbstractCompiler compiler) {
if (callNode.getType() != Token.NEW) {
throw new IllegalStateException(
"Expected NEW node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.getType() == Token.NAME &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
// A list of built-in object creation or primitive type cast functions that
// can also be called as constructors but lack side-effects.
// TODO(johnlenz): consider adding an extern annotation for this.
private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of(
"Object", "Array", "String", "Number", "Boolean", "RegExp", "Error");
private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of("toString", "valueOf");
private static final Set<String> REGEXP_METHODS =
ImmutableSet.of("test", "exec");
private static final Set<String> STRING_REGEXP_METHODS =
ImmutableSet.of("match", "replace", "search", "split");
/**
* Returns true if calls to this function have side effects.
*
* @param callNode - function call node
*/
static boolean functionCallHasSideEffects(
Node callNode) {
return functionCallHasSideEffects(callNode, null);
}
/**
* Returns true if calls to this function have side effects.
*
* @param callNode The call node to inspected.
* @param compiler A compiler object to provide program
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> state changing
* context information. Can be null.
*/
static boolean functionCallHasSideEffects(
Node callNode, @Nullable AbstractCompiler compiler) {
if (callNode.getType() != Token.CALL) {
throw new IllegalStateException(
"Expected CALL node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
// Built-in functions with no side effects.
if (nameNode.getType() == Token.NAME) {
String name = nameNode.getString();
if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) {
return false;
}
} else if (nameNode.getType() == Token.GETPROP) {
if (callNode.hasOneChild()
&& OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains(
nameNode.getLastChild().getString())) {
return false;
}
if (callNode.isOnlyModifiesThisCall()
&& evaluatesToLocalValue(nameNode.getFirstChild())) {
return false;
}
// Functions in the "Math" namespace have no side effects.
if (nameNode.getFirstChild().getType() == Token.NAME) {
String namespaceName = nameNode.getFirstChild().getString();
if (namespaceName.equals("Math")) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().getType() == Token.REGEXP
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
} else if (nameNode.getFirstChild().getType() == Token.STRING
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.getType() == Token.STRING
|| param.getType() == Token.REGEXP))
return false;
}
}
}
return true;
}
/**
* @return Whether the call has a local result.
*/
static boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.getType() == Token.CALL);
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHasSideEffects(n, compiler);
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set of names known to be constant value at
* node 'n' (such as locals that are last written before n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
case Token.FUNCTION:
// Function expression are not changed by side-effects,
// and function declarations are not part of expressions.
Preconditions.checkState(isFunctionExpression(n));
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
* 3 logical-or ||
* 4 logical-and &&
* 5 bitwise-or |
* 6 bitwise-xor ^
* 7 bitwise-and &
* 8 equality == !=
* 9 relational < <= > >=
* 10 bitwise shift << >> >>>
* 11 addition/subtraction + -
* 12 multiply/divide * / %
* 13 negation/increment ! ~ - ++ --
* 14 call, member () [] .
*/
static int precedence(int type) {
switch (type) {
case Token.COMMA: return 0;
case Token.ASSIGN_BITOR:
case
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN: return 1;
case Token.HOOK: return 2; // ?: operator
case Token.OR: return 3;
case Token.AND: return 4;
case Token.BITOR: return 5;
case Token.BITXOR: return 6;
case Token.BITAND: return 7;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: return 8;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN: return 9;
case Token.LSH:
case Token.RSH:
case Token.URSH: return 10;
case Token.SUB:
case Token.ADD: return 11;
case Token.MUL:
case Token.MOD:
case Token.DIV: return 12;
case Token.INC:
case Token.DEC:
case Token.NEW:
case Token.DELPROP:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.ARRAYLIT:
case Token.CALL:
case Token.EMPTY:
case Token.FALSE:
case Token.FUNCTION:
case Token.GETELEM:
case Token.GETPROP:
case Token.GET_REF:
case Token.IF:
case Token.LP:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.RETURN:
case Token.STRING:
case Token.THIS:
case Token.TRUE:
return 15;
default: throw new Error("Unknown precedence for " +
Node.tokenToName(type) +
" (type " + type + ")");
}
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITAND:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
return true;
default:
return false;
}
}
/**
* Returns true if the operator is commutative.
* e.g. (a * b) * c = c * (b * a)
* Note 1: "+" is not commutative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
* Note 2: only operations on literals and pure functions are commutative.
*/
static boolean isCommutative(int type) {
switch (type) {
case Token.MUL:
case Token.BITOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
case Token.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.URSH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assiment op");
}
static boolean isExpressionNode(Node n) {
return n.getType() == Token.EXPR_RESULT;
}
/**
* Determines if the given node contains a function statement or function
* expression.
*/
static boolean containsFunction(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the shallow scope contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
return containsType(n, Token.THIS, new MatchNotFunction());
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.getType() == Token.GETPROP
|| n.getType() == Token.GETELEM
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
}
/**
* Is this a GETPROP node?
*/
static boolean isGetProp(Node n) {
return n.getType() == Token.GETPROP;
}
/**
* Is this a NAME node?
*/
static boolean isName(Node n) {
return n.getType() == Token.NAME;
}
/**
* Is this a NEW node?
*/
static boolean isNew(Node n) {
return n.getType() == Token.NEW;
}
/**
* Is this a VAR node?
*/
static boolean isVar(Node n) {
return n.getType() == Token.VAR;
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.getType() == Token.NAME && n.getParent().getType() == Token.VAR;
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(isName(n));
Node parent = n.getParent();
if (isVar(parent)) {
return n.getFirstChild();
} else if (isAssign(parent) && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this a STRING node?
*/
static boolean isString(Node n) {
return n.getType() == Token.STRING;
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN;
}
/**
* Is this an ASSIGN node?
*/
static boolean isAssign(Node n) {
return n.getType() == Token.ASSIGN;
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.CALL;
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.getType() == Token.FOR
&& n.getChildCount() == 3;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (NodeUtil.isFunction(parent)) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK;
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
Node parent = n.getParent();
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.getType() == Token.CASE || n.getType() == Token.DEFAULT;
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return isName(n) && !n.getString().isEmpty();
}
/** @return Whether the node is a label name. */
static boolean isLabelName(Node n) {
return (n != null && n.getType() == Token.LABEL_NAME);
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.getType() == Token.TRY && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
// Node parent = node.getParent();
if (isStatementBlock(parent)
|| isSwitchCase(node)
|| isTryFinallyNode(parent, node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.getType() == Token.VAR) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent,
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (node.getType() == Token.BLOCK) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (parent.getType() == Token.LABEL
&& node == parent.getLastChild()) {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// A LABEL without children can not be referred to, remove it.
removeChild(parent.getParent(), parent);
} else if (parent.getType() == Token.FOR
&& parent.getChildCount() == 4) {
// Only Token.FOR can have an Token.EMPTY other control structure
// need something for the condition. Others need to be replaced
// or the structure removed.
parent.replaceChild(node, new Node(Token.EMPTY));
} else {
throw new IllegalStateException("Invalid attempt to remove node: " +
node.toString() + " of "+ parent.toString());
}
}
/**
* Merge a block with its parent block.
* @return Whether the block was removed.
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.getType() == Token.BLOCK);
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else {
return false;
}
}
/**
* Is this a CALL node?
*/
static boolean isCall(Node n) {
return n.getType() == Token.CALL;
}
/**
* @param node A node
* @return Whether the call is a NEW or CALL node.
*/
static boolean isCallOrNew(Node node) {
return NodeUtil.isCall(node) || NodeUtil.isNew(node);
}
/**
* Is this a FUNCTION node?
*/
static boolean isFunction(Node n) {
return n.getType() == Token.FUNCTION;
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(isFunction(fn));
return fn.getLastChild();
}
/**
* Is this a THIS node?
*/
static boolean isThis(Node node) {
return node.getType() == Token.THIS;
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.getType() == Token.FUNCTION && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().getType() == Token.SCRIPT
|| n.getParent().getParent().getType() == Token.FUNCTION);
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A node
* @return Whether n is an function used within an expression.
*/
static boolean isFunctionExpression(Node n) {
return n.getType() == Token.FUNCTION && !isStatement(n);
}
/**
* Determines if a node is a function expression that has an empty body.
*
* @param node a node
* @return whether the given node is a function expression that is empty
*/
static boolean isEmptyFunctionExpression(Node node) {
return isFunctionExpression(node) && isEmptyBlock(node.getLastChild());
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
Preconditions.checkArgument(isFunction(function));
return isNameReferenced(
function.getLastChild(),
"arguments",
new MatchNotFunction());
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode, String methodName) {
if (callNode.getType() == Token.CALL) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (isGet(functionIndentifyingExpression)) {
Node last = functionIndentifyingExpression.getLastChild();
if (last != null && last.getType() == Token.STRING) {
String propName = last
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.getString();
return (propName.equals(methodName));
}
}
}
return false;
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCall(Node callNode) {
return isObjectCallMethod(callNode, "call");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
*/
static boolean isFunctionObjectApply(Node callNode) {
return isObjectCallMethod(callNode, "apply");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
* or
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCallOrApply(Node callNode) {
return isFunctionObjectCall(callNode) || isFunctionObjectApply(callNode);
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
* where x is a NAME node.
*/
static boolean isSimpleFunctionObjectCall(Node callNode) {
if (isFunctionObjectCall(callNode)) {
if (callNode.getFirstChild().getFirstChild().getType() == Token.NAME) {
return true;
}
}
return false;
}
/**
* Determines whether this node is strictly on the left hand side of an assign
* or var initialization. Notably, this does not include all L-values, only
* statements where the node is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isLhs(Node n, Node parent) {
return (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) ||
parent.getType() == Token.VAR;
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
switch (node.getType()) {
case Token.NUMBER:
case Token.STRING:
return parent.getType() == Token.OBJECTLIT;
case Token.GET:
case Token.SET:
return true;
}
return false;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String op
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ToStr(int operator) {
switch (operator) {
case Token.BITOR: return "|";
case Token.OR: return "||";
case Token.BITXOR: return "^";
case Token.AND: return "&&";
case Token.BITAND: return "&";
case Token.SHEQ: return "===";
case Token.EQ: return "==";
case Token.NOT: return "!";
case Token.NE: return "!=";
case Token.SHNE: return "!==";
case Token.LSH: return "<<";
case Token.IN: return "in";
case Token.LE: return "<=";
case Token.LT: return "<";
case Token.URSH: return ">>>";
case Token.RSH: return ">>";
case Token.GE: return ">=";
case Token.GT: return ">";
case Token.MUL: return "*";
case Token.DIV: return "/";
case Token.MOD: return "%";
case Token.BITNOT: return "~";
case Token.ADD: return "+";
case Token.SUB: return "-";
case Token.POS: return "+";
case Token.NEG: return "-";
case Token.ASSIGN: return "=";
case Token.ASSIGN_BITOR: return "|=";
case Token.ASSIGN_BITXOR: return "^=";
case Token.ASSIGN_BITAND: return "&=";
case Token.ASSIGN_LSH: return "<<=";
case Token.ASSIGN_RSH: return ">>=";
case Token.ASSIGN_URSH: return ">>>=";
case Token.ASSIGN_ADD: return "+=";
case Token.ASSIGN_SUB: return "-=";
case Token.ASSIGN_MUL: return "*=";
case Token.ASSIGN_DIV: return "/=";
case Token.ASSIGN_MOD: return "%=";
case Token.VOID: return "void";
case Token.TYPEOF: return "typeof";
case Token.INSTANCEOF: return "instanceof";
default: return null;
}
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation or fails.
*
* @param operator the operator's token value to convert
* @return the string representation
* @throws Error if the token value is not an operator
*/
static String opToStrNoFail(int operator) {
String res = opToStr(operator);
if (res == null) {
throw new Error("Unknown op " + operator + ": " +
Token.name(operator));
}
return res;
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node,
int type,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean contains
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Type(Node node, int type) {
return containsType(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Given a node tree, finds all the VAR declarations in that tree that are
* not in an inner scope. Then adds a new VAR node at the top of the current
* scope that redeclares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = new Node(
Token.VAR,
Node.newString(Token.NAME, nameNode.getString())
.copyInformationFrom(nameNode))
.copyInformationFrom(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.getType() == Token.BLOCK ||
addingRoot.getType() == Token.SCRIPT);
Preconditions.checkState(addingRoot.getFirstChild() == null ||
addingRoot.getFirstChild().getType() != Token.SCRIPT);
return addingRoot;
}
/** Creates function name(params_0, ..., params_n) { body }. */
public static Node newFunctionNode(String name, List<Node> params,
Node body, int lineno, int charno) {
Node parameterParen = new Node(Token.LP, lineno, charno);
for (Node param : params) {
parameterParen.addChildToBack(param);
}
Node function = new Node(Token.FUNCTION, lineno, charno);
function.addChildrenToBack(
Node.newString(Token.NAME, name, lineno, charno));
function.addChildToBack(parameterParen);
function.addChildToBack(body);
return function;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.bar.baz")
* @param lineno The source line offset.
* @param charno The source character offset from start of the line.
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNode(
CodingConvention convention, String name, int lineno, int charno) {
int endPos = name.indexOf('.');
if (endPos == -1) {
return newName(convention, name, lineno, charno);
}
Node node = newName(
convention, name.substring(0, endPos), lineno, charno);
int startPos;
do {
startPos = endPos + 1;
endPos = name.indexOf('.', startPos);
String part = (endPos == -1
? name.substring(startPos)
: name.substring(startPos, endPos));
Node propNode = Node.newString(Token.STRING, part, lineno, charno);
if (convention.isConstantKey(part)) {
propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
node = new Node(Token.GETPROP, node, propNode, lineno, charno);
} while (endPos != -1);
return node;
}
/**
* Creates a node representing a qualified name, copying over the source
* location information from the basis node and assigning the given original
* name to the node.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @param basisNode The node that represents the name as currently found in
* the AST.
* @param originalName The original name of the item being represented by the
* NAME node. Used for debugging information.
*
* @return A NAME or GETPROP node
*/
static Node newQualifiedNameNode(
CodingConvention convention, String name, Node basisNode,
String originalName) {
Node node = newQualifiedNameNode(convention, name, -1, -1);
setDebugInformation(node, basisNode, originalName);
return node;
}
/**
* Gets the root node of a qualified name. Must be either NAME or THIS.
*/
static Node getRootOfQualifiedName(Node qName) {
for (Node current = qName; true;
current = current.getFirstChild()) {
int type = current.getType();
if (type == Token.NAME || type == Token.THIS) {
return current;
}
Preconditions.checkState(type == Token.GETPROP);
}
}
/**
* Sets the debug information (source file info and orignal name)
* on the given node.
*
* @param node The node on which to set the debug information.
* @param basisNode The basis node from which to copy the source file info.
* @param originalName The original name of the node.
*/
static void setDebugInformation(Node node, Node basisNode,
String originalName) {
node.copyInformationFromForTree(basisNode
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>);
node.putProp(Node.ORIGINALNAME_PROP, originalName);
}
private static Node newName(
CodingConvention convention, String name, int lineno, int charno) {
Node nameNode = Node.newString(Token.NAME, name, lineno, charno);
if (convention.isConstant(name)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
return nameNode;
}
/**
* Creates a new node representing an *existing* name, copying over the source
* location information from the basis node.
*
* @param name The name for the new NAME node.
* @param basisNode The node that represents the name as currently found in
* the AST.
*
* @return The node created.
*/
static Node newName(
CodingConvention convention, String name, Node basisNode) {
Node nameNode = Node.newString(Token.NAME, name);
if (convention.isConstantKey(name)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
nameNode.copyInformationFrom(basisNode);
return nameNode;
}
/**
* Creates a new node representing an *existing* name, copying over the source
* location information from the basis node and assigning the given original
* name to the node.
*
* @param name The name for the new NAME node.
* @param basisNode The node that represents the name as currently found in
* the AST.
* @param originalName The original name of the item being represented by the
* NAME node. Used for debugging information.
*
* @return The node created.
*/
static Node newName(
CodingConvention convention, String name,
Node basisNode, String originalName) {
Node nameNode = newName(convention, name, basisNode);
nameNode.putProp(Node.ORIGINALNAME_PROP, originalName);
return nameNode;
}
/** Test if all characters in the string are in the Basic Latin (aka ASCII)
* character set - that they have UTF-16 values equal to or below 0x7f.
* This check can find which identifiers with Unicode characters need to be
* escaped in order to allow resulting files to be processed by non-Unicode
* aware UNIX tools and editors.
* *
* See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
* for more on Basic Latin.
*
* @param s The string to be checked for ASCII-goodness.
*
* @return True if all characters in the string are in Basic Latin set.
*/
static boolean isLatin(String s) {
char LARGEST_BASIC_LATIN = 0x7f;
int len = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c > LARGEST_BASIC_LATIN) {
return
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> false;
}
}
return true;
}
/**
* Determines whether the given name can appear on the right side of
* the dot operator. Many properties (like reserved words) cannot.
*/
static boolean isValidPropertyName(String name) {
return TokenStream.isJSIdentifier(name) &&
!TokenStream.isKeyword(name) &&
// no Unicode escaped characters - some browsers are less tolerant
// of Unicode characters that might be valid according to the
// language spec.
// Note that by this point, unicode escapes have been converted
// to UTF-16 characters, so we're only searching for character
// values, not escapes.
isLatin(name);
}
private static class VarCollector implements Visitor {
final Map<String, Node> vars = Maps.newLinkedHashMap();
public void visit(Node n) {
if (n.getType() == Token.NAME) {
Node parent = n.getParent();
if (parent != null && parent.getType() == Token.VAR) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes.
*/
public static Collection<Node> getVarsDeclaredInBranch(Node root) {
VarCollector collector = new VarCollector();
visitPreOrder(
root,
collector,
new MatchNotFunction());
return collector.vars.values();
}
/**
* @return {@code true} if the node an assignment to a prototype property of
* some constructor.
*/
static boolean isPrototypePropertyDeclaration(Node n) {
if (!isExprAssign(n)) {
return false;
}
return isPrototypeProperty(n.getFirstChild().getFirstChild());
}
static boolean isPrototypeProperty(Node n) {
String lhsString = n.getQualifiedName();
if (lhsString == null) {
return false;
}
int prototypeIdx = lhsString.indexOf(".prototype.");
return prototypeIdx != -1;
}
/**
* @return The class name part of a qualified prototype name.
*/
static Node getPrototypeClassName(Node qName) {
Node cur = qName;
while (isGetProp(cur)) {
if (cur.getLastChild().getString().equals("prototype")) {
return cur.getFirstChild();
} else {
cur = cur.getFirstChild();
}
}
return null;
}
/**
* @return The string property name part of a qualified prototype name.
*/
static String getPrototypePropertyName(Node qName) {
String qNameStr = qName.getQualifiedName();
int prototypeIdx = qNameStr.lastIndexOf(".prototype.");
int memberIndex = prototypeIdx + ".prototype".length() + 1;
return qNameStr.substring(memberIndex);
}
/**
* Create a node for an empty result expression:
* "void 0"
*/
static
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Node newUndefinedNode(Node srcReferenceNode) {
// TODO(johnlenz): Why this instead of the more common "undefined"?
Node node = new Node(Token.VOID, Node.newNumber(0));
if (srcReferenceNode != null) {
node.copyInformationFromForTree(srcReferenceNode);
}
return node;
}
/**
* Create a VAR node containing the given name and initial value expression.
*/
static Node newVarNode(String name, Node value) {
Node nodeName = Node.newString(Token.NAME, name);
if (value != null) {
Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.copyInformationFrom(value);
}
Node var = new Node(Token.VAR, nodeName)
.copyInformationFrom(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
public boolean apply(Node n) {
return n.getType() == Token.NAME
&& n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.getType() == Token.VAR;
}
}
/**
* A predicate for matching anything except function nodes.
*/
static class MatchNotFunction implements Predicate<Node>{
public boolean apply(Node n) {
return !isFunction(n);
}
}
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
public boolean apply(Node n) {
Node parent = n.getParent();
return n.getType() == Token.BLOCK
|| (!isFunction(n) && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
String name,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNameNode(name), traverseChildrenPred);
}
/**
* Whether a simple name
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> is referenced within the node tree.
*/
static boolean isNameReferenced(Node node, String name) {
return isNameReferenced(node, name, Predicates.<Node>alwaysTrue());
}
/**
* Finds the number of times a simple name is referenced within the node tree.
*/
static int getNameReferenceCount(Node node, String name) {
return getCount(
node, new MatchNameNode(name), Predicates.<Node>alwaysTrue());
}
/**
* @return Whether the predicate is true for the node or any of its children.
*/
static boolean has(Node node,
Predicate<Node> pred,
Predicate<Node> traverseChildrenPred) {
if (pred.apply(node)) {
return true;
}
if (!traverseChildrenPred.apply(node)) {
return false;
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
if (has(c, pred, traverseChildrenPred)) {
return true;
}
}
return false;
}
/**
* @return The number of times the the predicate is true for the node
* or any of its children.
*/
static int getCount(
Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) {
int total = 0;
if (pred.apply(n)) {
total++;
}
if (traverseChildrenPred.apply(n)) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
total += getCount(c, pred, traverseChildrenPred);
}
}
return total;
}
/**
* Interface for use with the visit method.
* @see #visit
*/
static interface Visitor {
void visit(Node node);
}
/**
* A pre-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPreOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
vistor.visit(node);
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPreOrder(c, vistor, traverseChildrenPred);
}
}
}
/**
* A post-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPostOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPostOrder(c, vistor, traverseChildrenPred);
}
}
vistor.visit(node);
}
/**
* @return Whether a TRY node has a finally block.
*/
static boolean hasFinally(Node n) {
Preconditions.checkArgument(n.getType() == Token.TRY
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>);
return n.getChildCount() == 3;
}
/**
* @return The BLOCK node containing the CATCH node (if any)
* of a TRY.
*/
static Node getCatchBlock(Node n) {
Preconditions.checkArgument(n.getType() == Token.TRY);
return n.getFirstChild().getNext();
}
/**
* @return Whether BLOCK (from a TRY node) contains a CATCH.
* @see NodeUtil#getCatchBlock
*/
static boolean hasCatchHandler(Node n) {
Preconditions.checkArgument(n.getType() == Token.BLOCK);
return n.hasChildren() && n.getFirstChild().getType() == Token.CATCH;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
static Node getFnParameters(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION);
return fnNode.getFirstChild().getNext();
}
/**
* Returns true if a name node represents a constant variable.
*
* <p>Determining whether a variable is constant has three steps:
* <ol>
* <li>In CodingConventionAnnotator, any name that matches the
* {@link CodingConvention#isConstant(String)} is annotated with an
* IS_CONSTANT_NAME property.
* <li>The normalize pass renames any variable with the IS_CONSTANT_NAME
* annotation and that is initialized to a constant value with
* a variable name inlucding $$constant.
* <li>Return true here if the variable includes $$constant in its name.
* </ol>
*
* @param node A NAME or STRING node
* @return True if the variable is constant
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
String name = node.getString();
if (parent.getType() == Token.GETPROP &&
node == parent.getLastChild()) {
return convention.isConstantKey(name);
} else if (isObjectLitKey(node, parent)) {
return convention.isConstantKey(name);
} else {
return convention.isConstant(name);
}
}
/**
* @param nameNode A name node
* @return The JSDocInfo for the name node
*/
static JSDocInfo getInfoForNameNode(Node nameNode) {
JSDocInfo info = null;
Node parent = null;
if (nameNode != null) {
info = nameNode.getJSDocInfo();
parent = nameNode.getParent();
}
if (info == null && parent != null &&
((parent.getType() == Token.VAR && parent.hasOneChild
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>()) ||
parent.getType() == Token.FUNCTION)) {
info = parent.getJSDocInfo();
}
return info;
}
/**
* Get the JSDocInfo for a function.
*/
static JSDocInfo getFunctionInfo(Node n) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.getType() == Token.ASSIGN) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.getType() == Token.NAME) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
n = n.getParent();
}
return sourceName;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = isName(callTarget);
Node call = new Node(Token.CALL, callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case Token.ASSIGN:
// A result that is aliased by a non-local name, is the effectively the
// same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluates
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDECR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME:
return isImmutableValue(value) || locals.apply(value);
case Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value)
|| isToStringMethodCall(value)
|| locals.apply(value);
case Token.NEW:
return true;
case Token.FUNCTION:
case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.getNext();
index--;
}
return sibling;
}
/**
* Given the function, this returns the nth
* argument or null if no such parameter exists.
*/
static Node getArgumentForFunction(Node function, int index) {
Preconditions.checkState(isFunction(function));
return getNthSibling(
function.getFirstChild().getNext().getFirstChild(), index);
}
/**
* Given the new or call, this returns the nth
* argument of the call or null if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {
i++;
n = n / 10;
} while (n > 0);
return i;
}
/**
* Gets a string of spaces of the length specified.
* @param sb The string builder to append to.
* @param numSpaces The number of spaces in the string.
*/
@VisibleForTesting
static void appendSpaces(StringBuilder sb, int numSpaces) {
if (numSpaces > 16) {
logger.warning("Tracer.appendSpaces called with large numSpaces");
// Avoid long loop in case some bug in the caller
numSpaces = 16;
}
while (numSpaces >= 5) {
sb.append(" ");
numSpaces -= 5;
}
// We know it's less than 5 now
switch (numSpaces) {
case 1:
sb.append(" ");
break;
case 2:
sb.append(" ");
break;
case 3:
sb.append(" ");
break;
case 4:
sb.append(" ");
break;
}
}
/**
* Adds a new tracing statistic to a trace
*
* @param tracingStatistic to enable a run
* @return The index of this statistic (for use with stat.extraInfo()), or
* -1 if the statistic is not enabled.
*/
static int addTracingStatistic(TracingStatistic tracingStatistic) {
// Check to see if we can enable the tracing statistic before actually
// adding it.
if (tracingStatistic.enable()) {
// No synchronization needed, since this is a copy-on-write array.
extraTracingStatistics.add(tracingStatistic);
// 99.9% of the time, this will be O(1) and return extraTracingStatistics.length - 1
return extraTracingStatistics.lastIndexOf(tracingStatistic);
} else {
return -1;
}
}
/**
* For testing purposes only. These removes all current tracers. Severe errors can occur
* if there are any active tracers going on when this is called.
*
* The test suite uses this to remove any tracers that it has added.
*/
@VisibleForTesting
static void clearTracingStatisticsTestingOnly() {
extraTracingStatistics.clear();
}
/**
* Stop the trace.
* This may only be done once and must be done from the same thread
* that started it.
* @param silence_threshold Traces for time less than silence_threshold
* ms will be left out of the trace report. A value of -1 indicates
* that the current ThreadTrace silence_threshold should be used.
* @return The time that this trace actually ran
*/
long stop(int silence_threshold) {
Preconditions.checkState(Thread.currentThread() == startThread);
ThreadTrace trace = getThreadTrace();
// Do nothing if the thread trace was not initialized.
if (!trace.isInitialized()) {
return 0;
}
stopTimeMs = clock.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> sb.append(longToPaddedString(delta, digitsColWidth));
sb.append(" ms ");
if (tracer.extraTracingValues != null) {
for (int i = 0; i < tracer.extraTracingValues.length; i++) {
delta = tracer.extraTracingValues[i];
sb.append(String.format("%4d", delta));
sb.append(extraTracingStatistics.get(i).getUnits());
sb.append("; ");
}
}
}
sb.append(indent);
sb.append(tracer.toString());
return sb.toString();
}
}
/** Stores a thread's Trace */
static final class ThreadTrace {
/** Events taking less than this number of milliseconds are not reported. */
int defaultSilenceThreshold; // non-final
/** The Events corresponding to each startEvent/stopEvent */
final ArrayList<Event> events = new ArrayList<Event>();
/** Tracers that have not had their .stop() called */
final HashSet<Tracer> outstandingEvents = new HashSet<Tracer>();
/** Map from type to Stat object */
final Map<String, Stat> stats = new HashMap<String, Stat>();
/**
* True if {@code outstandingEvents} has been cleared because we exceeded
* the max trace limit.
*/
boolean isOutstandingEventsTruncated = false;
/**
* True if {@code events} has been cleared because we exceeded the max
* trace limit.
*/
boolean isEventsTruncated = false;
/**
* Set to true if {@link Tracer#initCurrentThreadTrace()} was called by
* the current thread.
*/
boolean isInitialized = false;
/**
* Whether pretty printing is enabled for the trace.
*/
boolean prettyPrint = false;
/** Initialize the trace. */
void init() {
isInitialized = true;
}
/** Is initialized? */
boolean isInitialized() {
return isInitialized;
}
/**
* Called by the constructor {@link Tracer#Tracer(String, String)} to create
* a start event.
*/
void startEvent(Tracer t) {
events.add(new Event(true, t));
boolean notAlreadyOutstanding = outstandingEvents.add(t);
Preconditions.checkState(notAlreadyOutstanding);
}
/**
* Called by {@link Tracer#stop()} to create a stop event.
*/
void endEvent(Tracer t, int silenceThreshold) {
boolean wasOutstanding = outstandingEvents.remove(t);
if (!wasOutstanding) {
if (isOutstandingEventsTruncated) {
// The events stack overflowed and was truncated, so just log a
// warning. Otherwise, we get an exception which is extremely
// confusing.
logger.log(Level.WARNING,
"event not found, probably because the event stack "
+ "overflowed and was truncated",
new Throwable());
} else {
// throw an exception if the event was not found and the events stack
// is pristine
throw new IllegalStateException();
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
long elapsed = t.stopTimeMs - t.startTimeMs;
if (silenceThreshold == -1) { // use default
silenceThreshold = defaultSilenceThreshold;
}
if (elapsed < silenceThreshold) {
// If this one is silent then we need to remove the start Event
boolean removed = false;
for (int i = 0; i < events.size(); i++) {
Event e = events.get(i);
if (e.tracer == t) {
Preconditions.checkState(e.isStart);
events.remove(i);
removed = true;
break;
}
}
// Only assert if we didn't find the original and the events
// weren't truncated.
Preconditions.checkState(removed || isEventsTruncated);
} else {
events.add(new Event(false, t));
}
if (t.type != null) {
Stat stat = stats.get(t.type);
if (stat == null) {
stat = new Stat();
if (!extraTracingStatistics.isEmpty()) {
stat.extraInfo = new int[extraTracingStatistics.size()];
}
stats.put(t.type, stat);
}
stat.count++;
if (typeToCountMap != null) {
typeToCountMap.incrementBy(t.type, 1);
}
stat.clockTime += elapsed;
if (typeToTimeMap != null) {
typeToTimeMap.incrementBy(t.type, elapsed);
}
if (stat.extraInfo != null && t.extraTracingValues != null) {
int overlapLength = Math.min(stat.extraInfo.length, t.extraTracingValues.length);
for (int i = 0; i < overlapLength; i++) {
stat.extraInfo[i] += t.extraTracingValues[i];
AtomicTracerStatMap map = extraTracingStatistics.get(i).getTracingStat();
if (map != null) {
map.incrementBy(t.type, t.extraTracingValues[i]);
}
}
}
if (elapsed < silenceThreshold) {
stat.silent++;
if (typeToSilentMap != null) {
typeToSilentMap.incrementBy(t.type, 1);
}
}
}
}
boolean isEmpty() {
return events.size() == 0 && outstandingEvents.size() == 0;
}
void truncateOutstandingEvents() {
isOutstandingEventsTruncated = true;
outstandingEvents.clear();
}
void truncateEvents() {
isEventsTruncated = true;
events.clear();
}
/** Produces the lovely Trace seen in the class comments */
// Nullness checker does not understand that prettyPrint => indent != null
@SuppressWarnings("nullness")
@Override public String toString() {
int numDigits = getMaxDigits();
StringBuilder sb = new StringBuilder();
long etime = -1;
LinkedList<String> indent = prettyPrint ? new LinkedList<
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {@link Candidate#canInline()} and finally perform inlining
* using {@link Candidate#inlineVariable()}.
*
* The reason for the delayed evaluation of the candidates is because we
* need two separate dataflow result.
*/
private final AbstractCompiler compiler;
// These two pieces of data is persistent in the whole execution of enter
// scope.
private ControlFlowGraph<Node> cfg;
private List<Candidate> candidates;
private MustBeReachingVariableDef reachingDef;
private MaybeReachingVariableUse reachingUses;
private static final Predicate<Node> SIDE_EFFECT_PREDICATE =
new Predicate<Node>() {
@Override
public boolean apply(Node n) {
// When the node is null it means, we reached the implicit return
// where the function returns (possibly without an return statement)
if (n == null) {
return false;
}
// TODO(user): We only care about calls to functions that
// passes one of the dependent variable to a non-sideeffect free
// function.
if (NodeUtil.isCall(n) && NodeUtil.functionCallHasSideEffects(n)) {
return true;
}
if (NodeUtil.isNew(n) && NodeUtil.constructorCallHasSideEffects(n)) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && apply(c)) {
return true;
}
}
return false;
}
};
public FlowSensitiveInlineVariables(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
return; // Don't even brother. All global variables are likely escaped.
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(NodeUtil.isFunction(t.getScopeRoot()));
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candiates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
if (c.canInline()) {
c.inlineVariable();
}
}
}
@Override
public void exitScope(NodeTraversal t) {}
@Override
public void process(Node
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> externs, Node root) {
(new NodeTraversal(compiler, this)).traverse(root);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// TODO(user): While the helpers do a subtree traversal on the AST, the
// compiler pass itself only traverse the AST to look for function
// declarations to perform dataflow analysis on. We could combine
// the traversal in DataFlowAnalysis's computeEscaped later to save some
// time.
}
/**
* Gathers a list of possible candidates for inlining based only on
* information from {@link MustBeReachingVariableDef}. The list will be stored
* in {@code candidiates} and the validity of each inlining Candidate should
* be later verified with {@link Candidate#canInline()} when
* {@link MaybeReachingVariableUse} has been performed.
*/
private class GatherCandiates extends AbstractShallowCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
DiGraphNode<Node, Branch> graphNode = cfg.getDirectedGraphNode(n);
if (graphNode == null) {
// Not a CFG node.
return;
}
FlowState<MustDef> state = graphNode.getAnnotation();
final MustDef defs = state.getIn();
final Node cfgNode = n;
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isName(n)) {
// Make sure that the name node is purely a read.
if ((NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| NodeUtil.isVar(parent) || parent.getType() == Token.INC ||
parent.getType() == Token.DEC || parent.getType() == Token.LP ||
parent.getType() == Token.CATCH) {
return;
}
String name = n.getString();
if (compiler.getCodingConvention().isExported(name)) {
return;
}
Node defNode = reachingDef.getDef(name, cfgNode);
if (defNode != null &&
!reachingDef.dependsOnOuterScopeVars(name, cfgNode)) {
candidates.add(new Candidate(name, defNode, n, cfgNode));
}
}
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Models the connection between a definition and a use of that definition.
*/
private class Candidate {
// Name of the variable.
private final String varName;
// Nodes related to the definition.
private Node def;
private final Node defCfgNode;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUse
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>CfgNode;
Candidate(String varName, Node defCfgNode, Node use, Node useCfgNode) {
Preconditions.checkArgument(NodeUtil.isName(use));
this.varName = varName;
this.defCfgNode = defCfgNode;
this.use = use;
this.useCfgNode = useCfgNode;
}
private boolean canInline() {
// Cannot inline a parameter.
if (NodeUtil.isFunction(defCfgNode)) {
return false;
}
getDefinition(defCfgNode, null);
getNumUseInUseCfgNode(useCfgNode, null);
// Definition was not found.
if (def == null) {
return false;
}
// Check that the assignment isn't used as a R-Value.
// TODO(user): Certain cases we can still inline.
if (NodeUtil.isAssign(def) && !NodeUtil.isExprAssign(def.getParent())) {
return false;
}
// The right of the definition has side effect:
// Example, for x:
// x = readProp(b), modifyProp(b); print(x);
if (checkRightOf(def, defCfgNode, SIDE_EFFECT_PREDICATE)) {
return false;
}
// Similar check as the above but this time, all the sub-expressions
// left of the use of the variable.
// x = readProp(b); modifyProp(b), print(x);
if (checkLeftOf(use, useCfgNode, SIDE_EFFECT_PREDICATE)) {
return false;
}
// Similar side effect check as above but this time the side effect is
// else where along the path.
// x = readProp(b); while(modifyProp(b)) {}; print(x);
CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>
pathCheck = new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>(
cfg,
cfg.getDirectedGraphNode(defCfgNode),
cfg.getDirectedGraphNode(useCfgNode),
SIDE_EFFECT_PREDICATE,
Predicates.
<DiGraphEdge<Node, ControlFlowGraph.Branch>>alwaysTrue(),
false);
if (pathCheck.somePathsSatisfyPredicate()) {
return false;
}
// TODO(user): Side-effect is ok sometimes. As long as there are no
// side-effect function down all paths to the use. Once we have all the
// side-effect analysis tool.
if (NodeUtil.mayHaveSideEffects(def.getLastChild())) {
return false;
}
// TODO(user): We could inline all the uses if the expression is short.
// Finally we have to make sure that there are no more than one use
// in the program and in the CFG node. Even when it is semantically
// correctly inlining twice increases code size.
if (numUseWithinUseCfgNode != 1) {
return false;
}
// Make sure that the name is not
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> within a loop
if (NodeUtil.isWithinLoop(use)) {
return false;
}
// We give up inling stuff with R-Value that has GETPROP, GETELEM,
// or anything that creates a new object.
// Example:
// var x = a.b.c; j.c = 1; print(x);
// Inlining print(a.b.c) is not safe consider j and be alias to a.b.
// TODO(user): We could get more accuracy by looking more in-detail
// what j is and what x is trying to into to.
if (NodeUtil.has(def.getLastChild(),
new Predicate<Node>() {
@Override
public boolean apply(Node input) {
switch (input.getType()) {
case Token.GETELEM:
case Token.GETPROP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.NEW:
return true;
}
return false;
}
},
new Predicate<Node>() {
@Override
public boolean apply(Node input) {
// Recurse if the node is not a function.
return !NodeUtil.isFunction(input);
}
})) {
return false;
}
Collection<Node> uses = reachingUses.getUses(varName, defCfgNode);
if (uses.size() != 1) {
return false;
}
return true;
}
/**
* Actual transformation.
*/
private void inlineVariable() {
Node defParent = def.getParent();
Node useParent = use.getParent();
if (NodeUtil.isAssign(def)) {
Node rhs = def.getLastChild();
rhs.detachFromParent();
// Oh yes! I have grandparent to remove this.
Preconditions.checkState(NodeUtil.isExpressionNode(defParent));
while (defParent.getParent().getType() == Token.LABEL) {
defParent = defParent.getParent();
}
defParent.detachFromParent();
useParent.replaceChild(use, rhs);
} else if (NodeUtil.isVar(defParent)) {
Node rhs = def.getLastChild();
def.removeChild(rhs);
useParent.replaceChild(use, rhs);
} else {
Preconditions.checkState(false, "No other definitions can be inlined.");
}
compiler.reportCodeChange();
}
/**
* Set the def node
*
* @param n A node that has a corresponding CFG node in the CFG.
*/
private void getDefinition(Node n, Node parent) {
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (n.getString().equals(varName) && n.hasChildren()) {
def = n;
}
return;
case Token.ASSIGN:
Node lhs = n
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.AbstractCompiler;
import com.google.javascript.jscomp.CompilerPass;
import com.google.javascript.jscomp.NodeTraversal;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
/**
* Transform the structure of the AST so that the number of explicit exits
* are minimized.
*
* @author johnlenz@google.com (John Lenz)
*/
class MinimizeExitPoints
extends AbstractPostOrderCallback
implements CompilerPass {
AbstractCompiler compiler;
MinimizeExitPoints(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.LABEL:
tryMinimizeExits(
n.getLastChild(), Token.BREAK, n.getFirstChild().getString());
break;
case Token.FOR:
case Token.WHILE:
tryMinimizeExits(
NodeUtil.getLoopCodeBlock(n), Token.CONTINUE, null);
break;
case Token.DO:
tryMinimizeExits(
NodeUtil.getLoopCodeBlock(n), Token.CONTINUE, null);
Node cond = NodeUtil.getConditionExpression(n);
if (NodeUtil.getBooleanValue(cond) == TernaryValue.FALSE) {
// Normally, we wouldn't be able to optimize BREAKs inside a loop
// but as we know the condition will always false, we can treat them
// as we would a CONTINUE.
tryMinimizeExits(
n.getFirstChild(), Token.BREAK, null);
}
break;
case Token.FUNCTION:
tryMinimizeExits(
n.getLastChild(), Token.RETURN, null);
break;
}
}
/**
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> * Attempts to minimize the number of explicit exit points in a control
* structure to take advantage of the implied exit at the end of the
* structure. This is accomplished by removing redundant statements, and
* moving statements following a qualifying IF node into that node.
* For example:
*
* function () {
* if (x) return;
* else blah();
* foo();
* }
*
* becomes:
*
* function () {
* if (x) ;
* else {
* blah();
* foo();
* }
*
* @param n The execution node of a parent to inspect.
* @param exitType The type of exit to look for.
* @param labelName If parent is a label the name of the label to look for,
* null otherwise.
* @nullable labelName non-null only for breaks within labels.
*/
void tryMinimizeExits(Node n, int exitType, String labelName) {
// Just an 'exit'.
if (matchingExitNode(n, exitType, labelName)) {
NodeUtil.removeChild(n.getParent(), n);
compiler.reportCodeChange();
return;
}
// Just an 'if'.
if (n.getType() == Token.IF) {
Node ifBlock = n.getFirstChild().getNext();
tryMinimizeExits(ifBlock, exitType, labelName);
Node elseBlock = ifBlock.getNext();
if (elseBlock != null) {
tryMinimizeExits(elseBlock, exitType, labelName);
}
return;
}
// Just a 'try/catch/finally'.
if (n.getType() == Token.TRY) {
Node tryBlock = n.getFirstChild();
tryMinimizeExits(tryBlock, exitType, labelName);
Node allCatchNodes = NodeUtil.getCatchBlock(n);
if (NodeUtil.hasCatchHandler(allCatchNodes)) {
Preconditions.checkState(allCatchNodes.hasOneChild());
Node catchNode = allCatchNodes.getFirstChild();
Node catchCodeBlock = catchNode.getLastChild();
tryMinimizeExits(catchCodeBlock, exitType, labelName);
}
if (NodeUtil.hasFinally(n)) {
Node finallyBlock = n.getLastChild();
tryMinimizeExits(finallyBlock, exitType, labelName);
}
}
// Just a 'label'.
if (n.getType() == Token.LABEL) {
Node labelBlock = n.getLastChild();
tryMinimizeExits(labelBlock, exitType, labelName);
}
// TODO(johnlenz): The last case of SWITCH statement?
// The rest assumes a block with at least one child, bail on anything else.
if (n.getType() != Token.BLOCK || n.getLastChild() == null) {
return;
}
// Multiple if-exits can be converted in a single pass.
// Convert
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> "if (blah) break; if (blah2) break; other_stmt;" to
// become "if (blah); else { if (blah2); else { other_stmt; } }"
// which will get converted to "if (!blah && !blah2) { other_stmt; }".
for (Node c : n.children()) {
// An 'if' block to process below.
if (c.getType() == Token.IF) {
Node ifTree = c;
Node trueBlock, falseBlock;
// First, the true condition block.
trueBlock = ifTree.getFirstChild().getNext();
falseBlock = trueBlock.getNext();
tryMinimizeIfBlockExits(trueBlock, falseBlock,
ifTree, exitType, labelName);
// Now the else block.
// The if blocks may have changed, get them again.
trueBlock = ifTree.getFirstChild().getNext();
falseBlock = trueBlock.getNext();
if (falseBlock != null) {
tryMinimizeIfBlockExits(falseBlock, trueBlock,
ifTree, exitType, labelName);
}
}
if (c == n.getLastChild()) {
break;
}
}
// Now try to minimize the exits of the last child, if it is removed
// look at what has become the last child.
for (Node c = n.getLastChild(); c != null; c = n.getLastChild()) {
tryMinimizeExits(c, exitType, labelName);
// If the node is still the last child, we are done.
if (c == n.getLastChild()) {
break;
}
}
}
/**
* Look for exits (returns, breaks, or continues, depending on the context) at
* the end of a block and removes them by moving the if node's siblings,
* if any, into the opposite condition block.
*
* @param srcBlock The block to inspect.
* @param destBlock The block to move sibling nodes into.
* @param ifNode The if node to work with.
* @param exitType The type of exit to look for.
* @param labelName The name associated with the exit, if any.
* @nullable labelName null for anything excepted for named-break associated
* with a label.
*/
private void tryMinimizeIfBlockExits(Node srcBlock, Node destBlock,
Node ifNode, int exitType, String labelName) {
Node exitNodeParent = null;
Node exitNode = null;
// Pick an exit node candidate.
if (srcBlock.getType() == Token.BLOCK) {
if (!srcBlock.hasChildren()) {
return;
}
exitNodeParent = srcBlock;
exitNode = exitNodeParent.getLastChild();
} else {
// Just a single statement, if it isn't an exit bail.
exitNodeParent = ifNode;
exitNode = srcBlock;
}
// Verify the candidate.
if (!matchingExitNode(exit
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Node, exitType, labelName)) {
return;
}
// Take case of the if nodes siblings, if any.
if (ifNode.getNext() != null) {
// Move siblings of the if block into the opposite
// logic block of the exit.
Node newDestBlock = new Node(Token.BLOCK).copyInformationFrom(ifNode);
if (destBlock == null) {
// Only possible if this is the false block.
ifNode.addChildToBack(newDestBlock);
} else if (destBlock.getType() == Token.EMPTY) {
// Use the new block.
ifNode.replaceChild(destBlock, newDestBlock);
} else if (destBlock.getType() == Token.BLOCK) {
// Reuse the existing block.
newDestBlock = destBlock;
} else {
// Add the existing statement to the new block.
ifNode.replaceChild(destBlock, newDestBlock);
newDestBlock.addChildToBack(destBlock);
}
// Move all the if node's following siblings.
moveAllFollowing(ifNode, ifNode.getParent(), newDestBlock);
}
// Get rid of the "exit", replace with an empty item if needed.
NodeUtil.removeChild(exitNodeParent, exitNode);
compiler.reportCodeChange();
}
/**
* Determines if n matches the type and name for the following types of
* "exits":
* - return without values
* - continues and breaks with or without names.
* @param n The node to inspect.
* @param type The Token type to look for.
* @param labelName The name that must be associated with the exit type.
* @nullable labelName non-null only for breaks associated with labels.
* @return Whether the node matches the specified block-exit type.
*/
static private boolean matchingExitNode(Node n, int type, String labelName) {
if (n.getType() == type) {
if (type == Token.RETURN) {
// only returns without expressions.
return !n.hasChildren();
} else {
if (labelName == null) {
return !n.hasChildren();
} else {
return n.hasChildren()
&& labelName.equals(n.getFirstChild().getString());
}
}
}
return false;
}
/**
* Move all the child nodes following start in srcParent to the end of
* destParent's child list.
* @param start The start point in the srcParent child list.
* @param srcParent The parent node of start.
* @param destParent The destination node.
*/
static private void moveAllFollowing(
Node start, Node srcParent, Node destParent) {
for (Node n = start.getNext(); n != null; n = start.getNext()) {
boolean isFunctionDeclaration =
NodeUtil.isFunctionDeclaration(n);
srcParent.removeChild(n);
if (isFunctionDeclaration) {
destParent.addChildToFront(n);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeUtil.MatchNotFunction;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
/**
* CodeGenerator generates codes from a parse tree, sending it to the specified
* CodeConsumer.
*
*/
class CodeGenerator {
private static final char[] HEX_CHARS
= { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private final CodeConsumer cc;
private final CharsetEncoder outputCharsetEncoder;
CodeGenerator(
CodeConsumer consumer, Charset outputCharset) {
cc = consumer;
if (outputCharset == null || outputCharset == Charsets.US_ASCII) {
// If we want our default (pretending to be UTF-8, but escaping anything
// outside of straight ASCII), then don't use the encoder, but
// just special-case the code. This keeps the normal path through
// the code identical to how it's been for years.
this.outputCharsetEncoder = null;
} else {
this.outputCharsetEncoder = outputCharset.newEncoder();
}
}
CodeGenerator(CodeConsumer consumer) {
this(consumer, null);
}
void add(String str) {
cc.add(str);
}
private void addIdentifier(String identifier) {
cc.addIdentifier(identifierEscape(identifier));
}
void add(Node n) {
add(n, Context.OTHER);
}
void add(Node n, Context context) {
if (!cc.continueProcessing()) {
return;
}
int type = n.getType();
String opstr = NodeUtil.opToStr(type);
int childCount = n.getChildCount();
Node first = n.getFirstChild();
Node last = n.getLastChild();
// Handle all binary
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> operators
if (opstr != null && first != last) {
Preconditions.checkState(
childCount == 2,
"Bad binary operator \"%s\": expected 2 arguments but got %s",
opstr, childCount);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p, context);
cc.addOp(opstr, true);
// For right-hand-side of operations, only pass context if it's
// the IN_FOR_INIT_CLAUSE one.
Context rhsContext = getContextForNoInOperator(context);
// Handle associativity.
// e.g. if the parse tree is a * (b * c),
// we can simply generate a * b * c.
if (last.getType() == type &&
NodeUtil.isAssociative(type)) {
addExpr(last, p, rhsContext);
} else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) {
// Assignments are the only right-associative binary operators
addExpr(last, p, rhsContext);
} else {
addExpr(last, p + 1, rhsContext);
}
return;
}
cc.startSourceMapping(n);
switch (type) {
case Token.TRY: {
Preconditions.checkState(first.getNext().getType() == Token.BLOCK &&
!first.getNext().hasMoreThanOneChild());
Preconditions.checkState(childCount >= 2 && childCount <= 3);
add("try");
add(first, Context.PRESERVE_BLOCK);
// second child contains the catch block, or nothing if there
// isn't a catch block
Node catchblock = first.getNext().getFirstChild();
if (catchblock != null) {
add(catchblock);
}
if (childCount == 3) {
add("finally");
add(last, Context.PRESERVE_BLOCK);
}
break;
}
case Token.CATCH:
Preconditions.checkState(childCount == 2);
add("catch(");
add(first);
add(")");
add(last, Context.PRESERVE_BLOCK);
break;
case Token.THROW:
Preconditions.checkState(childCount == 1);
add("throw");
add(first);
// Must have a ';' after a throw statement, otherwise safari can't
// parse this.
cc.endStatement(true);
break;
case Token.RETURN:
add("return");
if (childCount == 1) {
add(first);
} else {
Preconditions.checkState(childCount == 0);
}
cc.endStatement();
break;
case Token.VAR:
if (first != null) {
add("var ");
addList(first, false, getContextForNoInOperator(context));
}
break;
case Token.LABEL_NAME:
Preconditions.checkState(!n.getString().isEmpty());
addIdentifier(n.getString
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>());
break;
case Token.NAME:
if (first == null || first.getType() == Token.EMPTY) {
addIdentifier(n.getString());
} else {
Preconditions.checkState(childCount == 1);
addIdentifier(n.getString());
cc.addOp("=", true);
if (first.getType() == Token.COMMA) {
addExpr(first, NodeUtil.precedence(Token.ASSIGN));
} else {
// Add expression, consider nearby code at lowest level of
// precedence.
addExpr(first, 0, getContextForNoInOperator(context));
}
}
break;
case Token.ARRAYLIT:
add("[");
addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP));
add("]");
break;
case Token.LP:
add("(");
addList(first);
add(")");
break;
case Token.COMMA:
Preconditions.checkState(childCount == 2);
addList(first, false, context);
break;
case Token.NUMBER:
Preconditions.checkState(
childCount ==
((n.getParent() != null &&
n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0));
cc.addNumber(n.getDouble());
break;
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: {
// All of these unary operators are right-associative
Preconditions.checkState(childCount == 1);
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type));
break;
}
case Token.HOOK: {
Preconditions.checkState(childCount == 3);
int p = NodeUtil.precedence(type);
addLeftExpr(first, p + 1, context);
cc.addOp("?", true);
addExpr(first.getNext(), 1);
cc.addOp(":", true);
addExpr(last, 1);
break;
}
case Token.REGEXP:
if (first.getType() != Token.STRING ||
last.getType() != Token.STRING) {
throw new Error("Expected children to be strings");
}
String regexp = regexpEscape(first.getString(), outputCharsetEncoder);
// I only use one .add because whitespace matters
if (childCount == 2) {
add(regexp + last.getString());
} else {
Preconditions.checkState(childCount == 1);
add(regexp);
}
break;
case Token.GET_REF:
add(first);
break;
case Token.REF_SPECIAL:
Preconditions.checkState(childCount == 1);
add(first);
add(".");
add((String) n.getProp(Node.NAME_PROP));
break;
case Token.FUNCTION:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
Preconditions.checkState(childCount == 3);
boolean funcNeedsParens = (context == Context.START_OF_EXPR);
if (funcNeedsParens) {
add("(");
}
add("function");
add(first);
add(first.getNext());
add(last, Context.PRESERVE_BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.GET:
case Token.SET:
Preconditions.checkState(n.getParent().getType() == Token.OBJECTLIT);
Preconditions.checkState(childCount == 1);
Preconditions.checkState(first.getType() == Token.FUNCTION);
// Get methods are unnamed
Preconditions.checkState(first.getFirstChild().getString().isEmpty());
if (type == Token.GET) {
// Get methods have no parameters.
Preconditions.checkState(!first.getChildAtIndex(1).hasChildren());
add("get ");
} else {
// Set methods have one parameter.
Preconditions.checkState(first.getChildAtIndex(1).hasOneChild());
add("set ");
}
// The name is on the GET or SET node.
String name = n.getString();
Node fn = first;
Node parameters = fn.getChildAtIndex(1);
Node body = fn.getLastChild();
// Add the property name.
if (TokenStream.isJSIdentifier(name) &&
// do not encode literally any non-literal characters that were
// unicode escaped.
NodeUtil.isLatin(name)) {
add(name);
} else {
add(jsString(n.getString(), outputCharsetEncoder));
}
add(parameters);
add(body, Context.PRESERVE_BLOCK);
break;
case Token.SCRIPT:
case Token.BLOCK: {
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
boolean preserveBlock = context == Context.PRESERVE_BLOCK;
if (preserveBlock) {
cc.beginBlock();
}
boolean preferLineBreaks =
type == Token.SCRIPT ||
(type == Token.BLOCK &&
!preserveBlock &&
n.getParent() != null &&
n.getParent().getType() == Token.SCRIPT);
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.STATEMENT);
// VAR doesn't include ';' since it gets used in expressions
if (c.getType() == Token.VAR) {
cc.endStatement();
}
if (c.getType() == Token.FUNCTION) {
cc.maybeLineBreak();
}
// Prefer to break lines in between top-level statements
// because top level statements are more homogeneous.
if (preferLineBreaks) {
cc.notePreferredLineBreak();
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
}
if (preserveBlock) {
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
}
break;
}
case Token.FOR:
if (childCount == 4) {
add("for(");
if (first.getType() == Token.VAR) {
add(first, Context.IN_FOR_INIT_CLAUSE);
} else {
addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE);
}
add(";");
add(first.getNext());
add(";");
add(first.getNext().getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
Preconditions.checkState(childCount == 3);
add("for(");
add(first);
add("in");
add(first.getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
}
break;
case Token.DO:
Preconditions.checkState(childCount == 2);
add("do");
addNonEmptyStatement(first, Context.OTHER, false);
add("while(");
add(last);
add(")");
cc.endStatement();
break;
case Token.WHILE:
Preconditions.checkState(childCount == 2);
add("while(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.EMPTY:
Preconditions.checkState(childCount == 0);
break;
case Token.GETPROP: {
Preconditions.checkState(
childCount == 2,
"Bad GETPROP: expected 2 children, but got %s", childCount);
Preconditions.checkState(
last.getType() == Token.STRING,
"Bad GETPROP: RHS should be STRING");
boolean needsParens = (first.getType() == Token.NUMBER);
if (needsParens) {
add("(");
}
addLeftExpr(first, NodeUtil.precedence(type), context);
if (needsParens) {
add(")");
}
add(".");
addIdentifier(last.getString());
break;
}
case Token.GETELEM:
Preconditions.checkState(
childCount == 2,
"Bad GETELEM: expected 2 children but got %s", childCount);
addLeftExpr(first, NodeUtil.precedence(type), context);
add("[");
add(first.getNext());
add("]");
break;
case Token.WITH:
Preconditions.checkState(childCount == 2);
add("with(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.INC:
case Token.DEC: {
Preconditions.checkState(childCount == 1);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
String o = type == Token.INC ? "++" : "--";
int postProp = n.getIntProp(Node.INCRDECR_PROP);
// A non-zero post-prop value indicates a post inc/dec, default of zero
// is a pre-inc/dec.
if (postProp != 0) {
addLeftExpr(first, NodeUtil.precedence(type), context);
cc.addOp(o, false);
} else {
cc.addOp(o, false);
add(first);
}
break;
}
case Token.CALL:
// We have two special cases here:
// 1) If the left hand side of the call is a direct reference to eval,
// then it must have a DIRECT_EVAL annotation. If it does not, then
// that means it was originally an indirect call to eval, and that
// indirectness must be preserved.
// 2) If the left hand side of the call is a property reference,
// then the call must not a FREE_CALL annotation. If it does, then
// that means it was originally an call without an explicit this and
// that must be preserved.
if (isIndirectEval(first)
|| n.getBooleanProp(Node.FREE_CALL) && NodeUtil.isGet(first)) {
add("(0,");
addExpr(first, NodeUtil.precedence(Token.COMMA));
add(")");
} else {
addLeftExpr(first, NodeUtil.precedence(type), context);
}
add("(");
addList(first.getNext());
add(")");
break;
case Token.IF:
boolean hasElse = childCount == 3;
boolean ambiguousElseClause =
context == Context.BEFORE_DANGLING_ELSE && !hasElse;
if (ambiguousElseClause) {
cc.beginBlock();
}
add("if(");
add(first);
add(")");
if (hasElse) {
addNonEmptyStatement(
first.getNext(), Context.BEFORE_DANGLING_ELSE, false);
add("else");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
addNonEmptyStatement(first.getNext(), Context.OTHER, false);
Preconditions.checkState(childCount == 2);
}
if (ambiguousElseClause) {
cc.endBlock();
}
break;
case Token.NULL:
case Token.THIS:
case Token.FALSE:
case Token.TRUE:
Preconditions.checkState(childCount == 0);
add(Node.tokenToName(type));
break;
case Token.CONTINUE:
Preconditions.checkState(childCount <= 1);
add("continue");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> }
cc.endStatement();
break;
case Token.DEBUGGER:
Preconditions.checkState(childCount == 0);
add("debugger");
cc.endStatement();
break;
case Token.BREAK:
Preconditions.checkState(childCount <= 1);
add("break");
if (childCount == 1) {
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(" ");
add(first);
}
cc.endStatement();
break;
case Token.EXPR_VOID:
throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT.");
case Token.EXPR_RESULT:
Preconditions.checkState(childCount == 1);
add(first, Context.START_OF_EXPR);
cc.endStatement();
break;
case Token.NEW:
add("new ");
int precedence = NodeUtil.precedence(type);
// If the first child contains a CALL, then claim higher precedence
// to force parentheses. Otherwise, when parsed, NEW will bind to the
// first viable parentheses (don't traverse into functions).
if (NodeUtil.containsType(first, Token.CALL, new MatchNotFunction())) {
precedence = NodeUtil.precedence(first.getType()) + 1;
}
addExpr(first, precedence);
// '()' is optional when no arguments are present
Node next = first.getNext();
if (next != null) {
add("(");
addList(next);
add(")");
}
break;
case Token.STRING:
if (childCount !=
((n.getParent() != null &&
n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0)) {
throw new IllegalStateException(
"Unexpected String children: " + n.getParent().toStringTree());
}
add(jsString(n.getString(), outputCharsetEncoder));
break;
case Token.DELPROP:
Preconditions.checkState(childCount == 1);
add("delete ");
add(first);
break;
case Token.OBJECTLIT: {
boolean needsParens = (context == Context.START_OF_EXPR);
if (needsParens) {
add("(");
}
add("{");
for (Node c = first; c != null; c = c.getNext()) {
if (c != first) {
cc.listSeparator();
}
if (c.getType() == Token.GET || c.getType() == Token.SET) {
add(c);
} else {
// Object literal property names don't have to be quoted if they are
// not JavaScript keywords
if (c.getType() == Token.STRING &&
!TokenStream.isKeyword(c.getString()) &&
TokenStream.isJSIdentifier(c.getString()) &&
// do not encode literally any non-literal characters that were
// unicode escaped.
NodeUtil.isLatin(c.getString
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>())) {
add(c.getString());
} else {
addExpr(c, 1);
}
add(":");
addExpr(c.getFirstChild(), 1);
}
}
add("}");
if (needsParens) {
add(")");
}
break;
}
case Token.SWITCH:
add("switch(");
add(first);
add(")");
cc.beginBlock();
addAllSiblings(first.getNext());
cc.endBlock(context == Context.STATEMENT);
break;
case Token.CASE:
Preconditions.checkState(childCount == 2);
add("case ");
add(first);
addCaseBody(last);
break;
case Token.DEFAULT:
Preconditions.checkState(childCount == 1);
add("default");
addCaseBody(first);
break;
case Token.LABEL:
Preconditions.checkState(childCount == 2);
if (first.getType() != Token.LABEL_NAME) {
throw new Error("Unexpected token type. Should be LABEL_NAME.");
}
add(first);
add(":");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), true);
break;
// This node is auto generated in anonymous functions and should just get
// ignored for our purposes.
case Token.SETNAME:
break;
default:
throw new Error("Unknown type " + type + "\n" + n.toStringTree());
}
cc.endSourceMapping(n);
}
/**
* @return Whether the name is an indirect eval.
*/
private boolean isIndirectEval(Node n) {
return n.getType() == Token.NAME && "eval".equals(n.getString()) &&
!n.getBooleanProp(Node.DIRECT_EVAL);
}
/**
* Adds a block or expression, substituting a VOID with an empty statement.
* This is used for "for (...);" and "if (...);" type statements.
*
* @param n The node to print.
* @param context The context to determine how the node should be printed.
*/
private void addNonEmptyStatement(
Node n, Context context, boolean allowNonBlockChild) {
Node nodeToProcess = n;
if (!allowNonBlockChild && n.getType() != Token.BLOCK) {
throw new Error("Missing BLOCK child.");
}
// Strip unneeded blocks, that is blocks with <2 children unless
// the CodePrinter specifically wants to keep them.
if (n.getType() == Token.BLOCK) {
int count = getNonEmptyChildCount(n, 2);
if (count == 0) {
if (cc.shouldPreserveExtraBlocks()) {
cc.beginBlock();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
} else {
cc.endStatement(true);
}
return;
}
if (count == 1) {
// Hack around a couple of browser bugs:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
// Safari needs a block around function declarations.
// IE6/7 needs a block around DOs.
Node firstAndOnlyChild = getFirstNonEmptyChild(n);
boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks();
if (alwaysWrapInBlock || isOneExactlyFunctionOrDo(firstAndOnlyChild)) {
cc.beginBlock();
add(firstAndOnlyChild, Context.STATEMENT);
cc.maybeLineBreak();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
return;
} else {
// Continue with the only child.
nodeToProcess = firstAndOnlyChild;
}
}
if (count > 1) {
context = Context.PRESERVE_BLOCK;
}
}
if (nodeToProcess.getType() == Token.EMPTY) {
cc.endStatement(true);
} else {
add(nodeToProcess, context);
// VAR doesn't include ';' since it gets used in expressions - so any
// VAR in a statement context needs a call to endStatement() here.
if (nodeToProcess.getType() == Token.VAR) {
cc.endStatement();
}
}
}
/**
* @return Whether the Node is a DO or FUNCTION (with or without
* labels).
*/
private boolean isOneExactlyFunctionOrDo(Node n) {
if (n.getType() == Token.LABEL) {
Node labeledStatement = n.getLastChild();
if (labeledStatement.getType() != Token.BLOCK) {
return isOneExactlyFunctionOrDo(labeledStatement);
} else {
// For labels with block children, we need to ensure that a
// labeled FUNCTION or DO isn't generated when extraneous BLOCKs
// are skipped.
if (getNonEmptyChildCount(n, 2) == 1) {
return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n));
} else {
// Either a empty statement or an block with more than one child,
// way it isn't a FUNCTION or DO.
return false;
}
}
} else {
return (n.getType() == Token.FUNCTION || n.getType() == Token.DO);
}
}
/**
* Adds a node at the left-hand side of an expression. Unlike
* {@link #addExpr(Node,int)}, this preserves information about the context.
*
* The left side of an expression is special because in the JavaScript
* grammar, certain tokens may be parsed differently when they are at
* the beginning of a statement. For example, "{}" is parsed as a block,
* but "{'x': 'y'}" is parsed as an object literal.
*/
void addLeftExpr(Node n, int minPrecedence, Context context) {
addExpr(n, minPrecedence, context);
}
void addExpr(Node n, int minPrecedence) {
addExpr(n, minPrecedence, Context.OTHER);
}
private
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> void addExpr(Node n, int minPrecedence, Context context) {
if ((NodeUtil.precedence(n.getType()) < minPrecedence) ||
((context == Context.IN_FOR_INIT_CLAUSE) &&
(n.getType() == Token.IN))){
add("(");
add(n, clearContextForNoInOperator(context));
add(")");
} else {
add(n, context);
}
}
void addList(Node firstInList) {
addList(firstInList, true, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument) {
addList(firstInList, isArrayOrFunctionArgument, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument,
Context lhsContext) {
for (Node n = firstInList; n != null; n = n.getNext()) {
boolean isFirst = n == firstInList;
if (isFirst) {
addLeftExpr(n, isArrayOrFunctionArgument ? 1 : 0, lhsContext);
} else {
cc.listSeparator();
addExpr(n, isArrayOrFunctionArgument ? 1 : 0);
}
}
}
/**
* This function adds a comma-separated list as is specified by an ARRAYLIT
* node with the associated skipIndexes array. This is a space optimization
* since we avoid creating a whole Node object for each empty array literal
* slot.
* @param firstInList The first in the node list (chained through the next
* property).
* @param skipIndexes If not null, then the array of skipped entries in the
* array.
*/
void addList(Node firstInList, int[] skipIndexes) {
int nextSlot = 0;
int nextSkipSlot = 0;
for (Node n = firstInList; n != null; n = n.getNext()) {
while (skipIndexes != null && nextSkipSlot < skipIndexes.length) {
if (nextSlot == skipIndexes[nextSkipSlot]) {
cc.listSeparator();
nextSlot++;
nextSkipSlot++;
} else {
break;
}
}
if (n != firstInList) {
cc.listSeparator();
}
addExpr(n, 1);
nextSlot++;
}
}
void addCaseBody(Node caseBody) {
cc.beginCaseBody();
add(caseBody);
cc.endCaseBody();
}
void addAllSiblings(Node n) {
for (Node c = n; c != null; c = c.getNext()) {
add(c);
}
}
/** Outputs a js string, using the optimal (single/double) quote character */
static String jsString(String s, CharsetEncoder outputCharsetEncoder) {
int singleq = 0, doubleq = 0;
// could count the quotes and pick the optimal quote character
for (int i = 0; i
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> we're given an outputCharsetEncoder, then check if the
// character can be represented in this character set.
if (outputCharsetEncoder != null) {
if (outputCharsetEncoder.canEncode(c)) {
sb.append(c);
} else {
// Unicode-escape the character.
appendHexJavaScriptRepresentation(sb, c);
}
} else {
// No charsetEncoder provided - pass straight latin characters
// through, and escape the rest. Doing the explicit character
// check is measurably faster than using the CharsetEncoder.
if (c > 0x1f && c <= 0x7f) {
sb.append(c);
} else {
// Other characters can be misinterpreted by some js parsers,
// or perhaps mangled by proxies along the way,
// so we play it safe and unicode escape them.
appendHexJavaScriptRepresentation(sb, c);
}
}
}
}
sb.append(quote);
return sb.toString();
}
static String identifierEscape(String s) {
// First check if escaping is needed at all -- in most cases it isn't.
if (NodeUtil.isLatin(s)) {
return s;
}
// Now going through the string to escape non-latin characters if needed.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// Identifiers should always go to Latin1/ ASCII characters because
// different browser's rules for valid identifier characters are
// crazy.
if (c > 0x1F && c < 0x7F) {
sb.append(c);
} else {
appendHexJavaScriptRepresentation(sb, c);
}
}
return sb.toString();
}
/**
* @param maxCount The maximum number of children to look for.
* @return The number of children of this node that are non empty up to
* maxCount.
*/
private static int getNonEmptyChildCount(Node n, int maxCount) {
int i = 0;
Node c = n.getFirstChild();
for (; c != null && i < maxCount; c = c.getNext()) {
if (c.getType() == Token.BLOCK) {
i += getNonEmptyChildCount(c, maxCount-i);
} else if (c.getType() != Token.EMPTY) {
i++;
}
}
return i;
}
/** Gets the first non-empty child of the given node. */
private static Node getFirstNonEmptyChild(Node n) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.getType() == Token.BLOCK) {
Node result = getFirstNonEmptyChild(c);
if (result != null) {
return result;
}
} else if (c.getType() != Token.EMPTY) {
return c;
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
* The variable's doc info.
*/
private JSDocInfo info = null;
/**
* Whether the variable's type has been inferred or is declared. An inferred
* type may change over time (as more code is discovered), whereas a
* declared type is a static contract that must be matched.
*/
private final boolean typeInferred;
/** Input source */
CompilerInput input;
/** Whether the variable is a define */
boolean isDefine;
/**
* The index at which the var is declared. e..g if it's 0, it's the first
* declared variable in that scope
*/
int index;
/** The enclosing scope */
Scope scope;
/**
* Creates a variable.
*
* @param inferred whether its type is inferred (as opposed to declared)
*/
private Var(boolean inferred) {
this.typeInferred = inferred;
}
/**
* Gets the name of the variable.
*/
public String getName() {
return name;
}
/**
* Gets the parent of the name node.
*/
public Node getParentNode() {
return nameNode == null ? null : nameNode.getParent();
}
/**
* Gets the scope where this variable is declared.
*/
Scope getScope() {
return scope;
}
/**
* Returns the index within the scope stack.
* e.g. function Foo(a) { var b; function c(d) { } }
* a = 0, b = 1, c = 2, d = 3
*/
int getLocalVarIndex() {
int num = index;
Scope s = scope.getParent();
if (s == null) {
throw new IllegalArgumentException("Var is not local");
}
while (s.getParent() != null) {
num += s.getVarCount();
s = s.getParent();
}
return num;
}
/**
* Returns whether this is a global variable.
*/
public boolean isGlobal() {
return scope.isGlobal();
}
/**
* Returns whether this is a local variable.
*/
public boolean isLocal() {
return scope.isLocal();
}
/**
* Returns whether this is defined in an extern file.
*/
boolean isExtern() {
return input == null || input.isExtern();
}
/**
* Returns {@code true} if the variable is declared as a constant,
* based on the value reported by {@code NodeUtil}.
*/
public boolean isConst() {
return NodeUtil.isConstantName(nameNode);
}
/**
* Returns {@code true} if the variable is declared as a define.
* A variable is a define if it is annotaed by {@code @define}.
*/
public boolean isDefine() {
return isDefine;
}
public Node getInitialValue() {
Node parent = getParentNode();
int pType = parent.getType();
if (pType == Token.FUNCTION) {
return parent;
} else if (p
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Type == Token.ASSIGN) {
return parent.getLastChild();
} else if (pType == Token.VAR) {
return nameNode.getFirstChild();
} else {
return null;
}
}
/**
* Gets this variable's type. To know whether this type has been inferred,
* see {@code #isInferred()}.
*/
public JSType getType() {
return type;
}
/**
* Returns the name node that produced this variable.
*/
public Node getNameNode() {
return nameNode;
}
/**
* Gets the JSDocInfo for the variable.
*/
public JSDocInfo getJSDocInfo() {
return info;
}
/**
* Sets this variable's type.
* @throws IllegalStateException if the variable's type is not inferred
*/
void setType(JSType type) {
Preconditions.checkState(isTypeInferred());
this.type = type;
}
/**
* Resolve this variable's type.
*/
void resolveType(ErrorReporter errorReporter) {
if (type != null) {
type = type.resolve(errorReporter, scope);
}
}
/**
* Returns whether this variable's type is inferred. To get the variable's
* type, see {@link #getType()}.
*/
public boolean isTypeInferred() {
return typeInferred;
}
public String getInputName() {
if (input == null)
return "<non-file>";
else
return input.getName();
}
public boolean isNoShadow() {
if (info != null && info.isNoShadow()) {
return true;
} else {
return false;
}
}
@Override public boolean equals(Object other) {
if (!(other instanceof Var)) {
return false;
}
Var otherVar = (Var) other;
return otherVar.nameNode == nameNode;
}
@Override public int hashCode() {
return nameNode.hashCode();
}
@Override
public String toString() {
return "Scope.Var " + name;
}
}
/**
* Creates a Scope given the parent Scope and the root node of the scope.
* @param parent The parent Scope. Cannot be null.
* @param rootNode Typically the FUNCTION node.
*/
Scope(Scope parent, Node rootNode) {
Preconditions.checkNotNull(parent);
Preconditions.checkArgument(rootNode != parent.rootNode);
this.parent = parent;
this.rootNode = rootNode;
JSType nodeType = rootNode.getJSType();
if (nodeType != null && nodeType instanceof FunctionType) {
thisType = ((FunctionType) nodeType).getTypeOfThis();
} else {
thisType = parent.thisType;
}
this.isBottom = false;
}
/**
* Creates a global Scope.
* @param rootNode Typically the global BLOCK node.
*/
Scope(Node rootNode, AbstractCompiler compiler) {
this.parent = null;
this.root
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Node = rootNode;
thisType = compiler.getTypeRegistry().getNativeObjectType(GLOBAL_THIS);
this.isBottom = false;
}
/**
* Creates a empty Scope (bottom of the lattice).
* @param rootNode Typically a FUNCTION node or the global BLOCK node.
* @param thisType the type of {@code this} in this scope
*/
Scope(Node rootNode, ObjectType thisType) {
this.parent = null;
this.rootNode = rootNode;
this.thisType = thisType;
this.isBottom = true;
}
/** Whether this is the bottom of the lattice. */
boolean isBottom() {
return isBottom;
}
/**
* Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node.
*/
public Node getRootNode() {
return rootNode;
}
public Scope getParent() {
return parent;
}
Scope getGlobalScope() {
Scope result = this;
while (result.getParent() != null) {
result = result.getParent();
}
return result;
}
@Override
public StaticScope<JSType> getParentScope() {
return parent;
}
/**
* Gets the type of {@code this} in the current scope.
*/
public ObjectType getTypeOfThis() {
return thisType;
}
/**
* Declares a variable whose type is inferred.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
*/
Var declare(String name, Node nameNode, JSType type, CompilerInput input) {
return declare(name, nameNode, type, input, true);
}
/**
* Declares a variable.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
* @param inferred Whether this variable's type is inferred (as opposed
* to declared).
*/
Var declare(String name, Node nameNode,
JSType type, CompilerInput input, boolean inferred) {
Preconditions.checkState(name != null && name.length() > 0);
// Make sure that it's declared only once
Preconditions.checkState(vars.get(name) == null);
Var var = new Var(inferred);
var.name = name;
var.nameNode = nameNode;
var.type = type;
var.scope = this;
var.index = vars.size();
var.input = input;
// native variables do not have a name node.
// TODO(user): make Var abstract and have NativeVar, NormalVar.
JSDocInfo info = NodeUtil.getInfoForNameNode(nameNode);
var.isDefine = info != null && info.isDefine();
var.info
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> = info;
vars.put(name, var);
return var;
}
/**
* Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope.
*/
void undeclare(Var var) {
Preconditions.checkState(var.scope == this);
Preconditions.checkState(vars.get(var.name) == var);
vars.remove(var.name);
}
public StaticSlot<JSType> getSlot(String name) {
return getVar(name);
}
public StaticSlot<JSType> getOwnSlot(String name) {
return vars.get(name);
}
/**
* Returns the variable, may be null
*/
public Var getVar(String name) {
Var var = vars.get(name);
if (var != null) {
return var;
} else if (parent != null) { // Recurse up the parent Scope
return parent.getVar(name);
} else {
return null;
}
}
/**
* Returns true if a variable is declared.
*/
public boolean isDeclared(String name, boolean recurse) {
Scope scope = this;
if (scope.vars.containsKey(name))
return true;
if (scope.parent != null && recurse) {
return scope.parent.isDeclared(name, recurse);
}
return false;
}
/**
* Return an iterator over all of the variables declared in this scope.
*/
public Iterator<Var> getVars() {
return vars.values().iterator();
}
/**
* Returns number of variables in this scope
*/
public int getVarCount() {
return vars.size();
}
/**
* Returns whether this is the global scope.
*/
public boolean isGlobal() {
return parent == null;
}
/**
* Returns whether this is a local scope (i.e. not the global scope).
*/
public boolean isLocal() {
return !isGlobal();
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>, entering from the given node.
* @param graph The graph to traverse.
* @param entry The node to begin traversing from.
*/
public void computeFixedPoint(DiGraph<N, E> graph, N entry) {
Set<N> entrySet = Sets.newHashSet();
entrySet.add(entry);
computeFixedPoint(graph, entrySet);
}
/**
* Compute a fixed point for the given graph, entering from the given nodes.
* @param graph The graph to traverse.
* @param entrySet The nodes to begin traversing from.
*/
public void computeFixedPoint(DiGraph<N, E> graph, Set<N> entrySet) {
int cycleCount = 0;
long nodeCount = graph.getNodes().size();
// Choose a bail-out heuristically in case the computation
// doesn't converge.
long maxIterations = Math.max(nodeCount * nodeCount * nodeCount, 100);
// Use a LinkedHashSet, so that the traversal is deterministic.
LinkedHashSet<DiGraphNode<N, E>> workSet =
Sets.newLinkedHashSet();
for (N n : entrySet) {
workSet.add(graph.getDirectedGraphNode(n));
}
for (; !workSet.isEmpty() && cycleCount < maxIterations; cycleCount++) {
// For every out edge in the workSet, traverse that edge. If that
// edge updates the state of the graph, then add the destination
// node to the resultSet, so that we can update all of its out edges
// on the next iteration.
DiGraphNode<N, E> source = workSet.iterator().next();
N sourceValue = source.getValue();
workSet.remove(source);
List<DiGraphEdge<N, E>> outEdges = source.getOutEdges();
for (DiGraphEdge<N, E> edge : outEdges) {
N destNode = edge.getDestination().getValue();
if (callback.traverseEdge(sourceValue, edge.getValue(), destNode)) {
workSet.add(edge.getDestination());
}
}
}
Preconditions.checkState(cycleCount != maxIterations,
NON_HALTING_ERROR_MSG);
}
public static interface EdgeCallback<Node, Edge> {
/**
* Update the state of the destination node when the given edge
* is traversed. For the fixed-point computation to work, only the
* destination node may be modified. The source node and the edge must
* not be modified.
*
* @param source The start node.
* @param e The edge.
* @param destination The end node.
* @return Whether the state of the destination node changed.
*/
boolean traverseEdge(Node source, Edge e, Node destination);
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Checks for misplaced semicolons, such as
* <pre>
* if (foo()); act_now();
* </pre>
* and generates warnings.
*
*/
final class CheckAccidentalSemicolon extends AbstractPostOrderCallback {
static final DiagnosticType SUSPICIOUS_SEMICOLON = DiagnosticType.warning(
"JSC_SUSPICIOUS_SEMICOLON",
"If this if/for/while really shouldn't have a body, use {}");
private final CheckLevel level;
CheckAccidentalSemicolon(CheckLevel level) {
this.level = level;
}
public void visit(NodeTraversal t, Node n, Node parent) {
Node child;
switch (n.getType()) {
case Token.IF:
child = n.getFirstChild().getNext(); // skip the condition child
break;
case Token.WHILE:
case Token.FOR:
child = NodeUtil.getLoopCodeBlock(n);
break;
default:
return; // don't check other types
}
// semicolons cause VOID children. Empty blocks are allowed because
// that's usually intentional, especially with loops.
for (; child != null; child = child.getNext()) {
if ((child.getType() == Token.BLOCK) && (!child.hasChildren())) {
// Only warn on empty blocks that replaced EMPTY nodes. BLOCKs with no
// children are considered OK.
if (child.wasEmptyNode()) {
t.getCompiler().report(
t.makeError(n, level, SUSPICIOUS_SEMICOLON));
}
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* A simple pass to ensure that all AST nodes have line numbers,
* an that the line numbers are monotonically increasing.
*
* @author nicksantos@google.com (Nick Santos)
*/
class LineNumberCheck implements Callback, CompilerPass {
static final DiagnosticType MISSING_LINE_INFO = DiagnosticType.error(
"JSC_MISSING_LINE_INFO",
"No source location information associated with {0}.\n" +
"Most likely a Node has been created with settings the source file " +
"and line/column location. Usually this is done using " +
"Node.copyInformationFrom and supplying a Node from the source AST.");
private final AbstractCompiler compiler;
private boolean requiresLineNumbers = false;
LineNumberCheck(AbstractCompiler compiler) {
this.compiler = compiler;
}
public void setCheckSubTree(Node root) {
requiresLineNumbers = true;
NodeTraversal.traverse(compiler, root, this);
}
public void process(Node externs, Node root) {
requiresLineNumbers = false;
NodeTraversal.traverse(compiler, root, this);
}
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
// Each JavaScript file is rooted in a script node, so we'll only
// have line number information inside the script node.
if (n.getType() == Token.SCRIPT) {
requiresLineNumbers = true;
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.SCRIPT) {
requiresLineNumbers = false;
} else if (requiresLineNumbers) {
if (n.getLineno() == -1) {
// The tree version of the node is really the best diagnostic
// info we have to offer here.
compiler.report(
t.makeError(n, MISSING_LINE_INFO,
n.toStringTree()));
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Node errorRoot, String sourceName, Scope scope) {
Preconditions.checkNotNull(errorRoot);
this.fnName = fnName == null ? "" : fnName;
this.codingConvention = compiler.getCodingConvention();
this.typeRegistry = compiler.getTypeRegistry();
this.errorRoot = errorRoot;
this.sourceName = sourceName;
this.compiler = compiler;
this.scope = scope;
}
/**
* Sets the FUNCTION node of this function.
*/
FunctionTypeBuilder setSourceNode(@Nullable Node sourceNode) {
this.sourceNode = sourceNode;
return this;
}
/**
* Infer the parameter and return types of a function from
* the parameter and return types of the function it is overriding.
*
* @param oldType The function being overridden. Does nothing if this is null.
* @param paramsParent The LP node of the function that we're assigning to.
* If null, that just means we're not initializing this to a function
* literal.
*/
FunctionTypeBuilder inferFromOverriddenFunction(
@Nullable FunctionType oldType, @Nullable Node paramsParent) {
if (oldType == null) {
return this;
}
returnType = oldType.getReturnType();
returnTypeInferred = oldType.isReturnTypeInferred();
if (paramsParent == null) {
// Not a function literal.
parametersNode = oldType.getParametersNode();
if (parametersNode == null) {
parametersNode = new FunctionParamBuilder(typeRegistry).build();
}
} else {
// We're overriding with a function literal. Apply type information
// to each parameter of the literal.
FunctionParamBuilder paramBuilder =
new FunctionParamBuilder(typeRegistry);
Iterator<Node> oldParams = oldType.getParameters().iterator();
boolean warnedAboutArgList = false;
boolean oldParamsListHitOptArgs = false;
for (Node currentParam = paramsParent.getFirstChild();
currentParam != null; currentParam = currentParam.getNext()) {
if (oldParams.hasNext()) {
Node oldParam = oldParams.next();
Node newParam = paramBuilder.newParameterFromNode(oldParam);
oldParamsListHitOptArgs = oldParamsListHitOptArgs ||
oldParam.isVarArgs() ||
oldParam.isOptionalArg();
// The subclass method might right its var_args as individual
// arguments.
if (currentParam.getNext() != null && newParam.isVarArgs()) {
newParam.setVarArgs(false);
newParam.setOptionalArg(true);
}
} else {
warnedAboutArgList |= addParameter(
paramBuilder,
typeRegistry.getNativeType(UNKNOWN_TYPE),
warnedAboutArgList,
codingConvention.isOptionalParameter(currentParam) ||
oldParamsListHitOptArgs,
codingConvention.isVarArgsParameter(currentParam));
}
}
parametersNode = paramBuilder.build();
}
return this;
}
/**
* Infer the
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> return type from JSDocInfo.
*/
FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) {
if (info != null && info.hasReturnType()) {
returnType = info.getReturnType().evaluate(scope, typeRegistry);
returnTypeInferred = false;
}
if (templateTypeName != null &&
returnType != null &&
returnType.restrictByNotNullOrUndefined().isTemplateType()) {
reportError(TEMPLATE_TYPE_EXPECTED, fnName);
}
return this;
}
/**
* If we haven't found a return value yet, try to look at the "return"
* statements in the function.
*/
FunctionTypeBuilder inferReturnStatementsAsLastResort(
@Nullable Node functionBlock) {
if (functionBlock == null || compiler.getInput(sourceName).isExtern()) {
return this;
}
Preconditions.checkArgument(functionBlock.getType() == Token.BLOCK);
if (returnType == null) {
boolean hasNonEmptyReturns = false;
List<Node> worklist = Lists.newArrayList(functionBlock);
while (!worklist.isEmpty()) {
Node current = worklist.remove(worklist.size() - 1);
int cType = current.getType();
if (cType == Token.RETURN && current.getFirstChild() != null ||
cType == Token.THROW) {
hasNonEmptyReturns = true;
break;
} else if (NodeUtil.isStatementBlock(current) ||
NodeUtil.isControlStructure(current)) {
for (Node child = current.getFirstChild();
child != null; child = child.getNext()) {
worklist.add(child);
}
}
}
if (!hasNonEmptyReturns) {
returnType = typeRegistry.getNativeType(VOID_TYPE);
returnTypeInferred = true;
}
}
return this;
}
/**
* Infer the role of the function (whether it's a constructor or interface)
* and what it inherits from in JSDocInfo.
*/
FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) {
if (info != null) {
isConstructor = info.isConstructor();
isInterface = info.isInterface();
// base type
if (info.hasBaseType()) {
if (isConstructor || isInterface) {
JSType maybeBaseType =
info.getBaseType().evaluate(scope, typeRegistry);
if (maybeBaseType != null &&
maybeBaseType.setValidator(new ExtendedTypeValidator())) {
baseType = (ObjectType) maybeBaseType;
}
} else {
reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName);
}
}
// implemented interfaces
if (isConstructor || isInterface) {
implementedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
JSType maybeInterType = t.evaluate(scope, typeRegistry);
if (maybeInterType != null &&
maybeInterType.setValidator(new ImplementedTypeValidator())) {
implementedInterfaces.add((
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ObjectType) maybeInterType);
}
}
if (baseType != null) {
JSType maybeFunctionType = baseType.getConstructor();
if (maybeFunctionType instanceof FunctionType) {
FunctionType functionType = baseType.getConstructor();
Iterables.addAll(
implementedInterfaces,
functionType.getImplementedInterfaces());
}
}
} else if (info.getImplementedInterfaceCount() > 0) {
reportWarning(IMPLEMENTS_WITHOUT_CONSTRUCTOR, fnName);
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param type The type of this.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) {
ObjectType objType = ObjectType.cast(type);
if (objType != null && (info == null || !info.hasType())) {
thisType = objType;
}
return this;
}
/**
* Infers the type of {@code this}.
* @param info The JSDocInfo for this function.
* @param owner The node for the object whose prototype "owns" this function.
* For example, {@code A} in the expression {@code A.prototype.foo}. May
* be null to indicate that this is not a prototype property.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info,
@Nullable Node owner) {
ObjectType maybeThisType = null;
if (info != null && info.hasThisType()) {
maybeThisType = ObjectType.cast(
info.getThisType().evaluate(scope, typeRegistry));
}
if (maybeThisType != null) {
thisType = maybeThisType;
thisType.setValidator(new ThisTypeValidator());
} else if (owner != null &&
(info == null || !info.hasType())) {
// If the function is of the form:
// x.prototype.y = function() {}
// then we can assume "x" is the @this type. On the other hand,
// if it's of the form:
// /** @type {Function} */ x.prototype.y;
// then we should not give it a @this type.
String ownerTypeName = owner.getQualifiedName();
ObjectType ownerType = ObjectType.cast(
typeRegistry.getForgivingType(
scope, ownerTypeName, sourceName,
owner.getLineno(), owner.getCharno()));
if (ownerType != null) {
thisType = ownerType;
}
}
return this;
}
/**
* Infer the parameter types from the doc info alone.
*/
FunctionTypeBuilder inferParameterTypes(JSDocInfo info) {
// Create a fake args parent.
Node lp = new Node(Token.LP);
for (String name : info.getParameterNames()) {
lp.addChildToBack(Node.newString(Token.NAME, name));
}
return inferParameterTypes(lp, info);
}
/**
* Infer the parameter
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) {
typeRegistry.declareType(fnName, fnType.getInstanceType());
}
maybeSetBaseType(fnType);
} else {
fnType = new FunctionBuilder(typeRegistry)
.withName(fnName)
.withSourceNode(sourceNode)
.withParamsNode(parametersNode)
.withReturnType(returnType, returnTypeInferred)
.withTypeOfThis(thisType)
.withTemplateName(templateTypeName)
.build();
maybeSetBaseType(fnType);
}
if (implementedInterfaces != null) {
fnType.setImplementedInterfaces(implementedInterfaces);
}
typeRegistry.clearTemplateTypeName();
return fnType;
}
private void maybeSetBaseType(FunctionType fnType) {
if (baseType != null) {
fnType.setPrototypeBasedOn(baseType);
}
}
/**
* Returns a constructor function either by returning it from the
* registry if it exists or creating and registering a new type. If
* there is already a type, then warn if the existing type is
* different than the one we are creating, though still return the
* existing function if possible. The primary purpose of this is
* that registering a constructor will fail for all built-in types
* that are initialized in {@link JSTypeRegistry}. We a) want to
* make sure that the type information specified in the externs file
* matches what is in the registry and b) annotate the externs with
* the {@link JSType} from the registry so that there are not two
* separate JSType objects for one type.
*/
private FunctionType getOrCreateConstructor() {
FunctionType fnType = typeRegistry.createConstructorType(
fnName, sourceNode, parametersNode, returnType);
JSType existingType = typeRegistry.getType(fnName);
if (existingType != null) {
boolean isInstanceObject = existingType instanceof InstanceObjectType;
if (isInstanceObject || fnName.equals("Function")) {
FunctionType existingFn =
isInstanceObject ?
((InstanceObjectType) existingType).getConstructor() :
typeRegistry.getNativeFunctionType(FUNCTION_FUNCTION_TYPE);
if (existingFn.getSource() == null) {
existingFn.setSource(sourceNode);
}
if (!existingFn.hasEqualCallType(fnType)) {
reportWarning(TYPE_REDEFINITION, fnName,
fnType.toString(), existingFn.toString());
}
return existingFn;
} else {
// We fall through and return the created type, even though it will fail
// to register. We have no choice as we have to return a function. We
// issue an error elsewhere though, so the user should fix it.
}
}
maybeSetBaseType(fnType);
if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) {
typeRegistry.declareType(fnName, fnType.getInstanceType());
}
return fnType;
}
private void reportWarning
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> to descendant names should emit warnings.
*/
private void checkDescendantNames(Name name, boolean nameIsDefined) {
if (name.props != null) {
for (Name prop : name.props) {
// if the ancestor of a property is not defined, then we should emit
// warnings for all references to the property.
boolean propIsDefined = false;
if (nameIsDefined) {
// if the ancestor of a property is defined, then let's check that
// the property is also explicitly defined if it needs to be.
propIsDefined = (!propertyMustBeInitializedByFullName(prop) ||
prop.globalSets + prop.localSets > 0);
}
validateName(prop, propIsDefined);
checkDescendantNames(prop, propIsDefined);
}
}
}
private void validateName(Name name, boolean isDefined) {
// If the name is not defined, emit warnings for each reference. While
// we're looking through each reference, check all the module dependencies.
Ref declaration = name.declaration;
if (!isDefined) {
if (declaration != null) {
reportRefToUndefinedName(name, declaration);
}
}
if (name.refs != null) {
JSModuleGraph moduleGraph = compiler.getModuleGraph();
for (Ref ref : name.refs) {
if (!isDefined) {
reportRefToUndefinedName(name, ref);
} else {
if (declaration != null &&
ref.module != declaration.module &&
!moduleGraph.dependsOn(ref.module, declaration.module)) {
reportBadModuleReference(name, ref);
}
}
}
}
}
private void reportBadModuleReference(Name name, Ref ref) {
compiler.report(
JSError.make(ref.sourceName, ref.node, STRICT_MODULE_DEP_QNAME,
ref.module.getName(), name.declaration.module.getName(),
name.fullName()));
}
private void reportRefToUndefinedName(Name name, Ref ref) {
// grab the highest undefined ancestor to output in the warning message.
while (name.parent != null &&
name.parent.globalSets + name.parent.localSets == 0) {
name = name.parent;
}
// If this is an annotated EXPR-GET, don't do anything.
Node parent = ref.node.getParent();
if (parent.getType() == Token.EXPR_RESULT) {
JSDocInfo info = ref.node.getJSDocInfo();
if (info != null && info.hasTypedefType()) {
return;
}
}
compiler.report(
JSError.make(ref.sourceName, ref.node, level, UNDEFINED_NAME_WARNING,
name.fullName()));
}
/**
* Checks whether the given name is a property, and whether that property
* must be initialized with its full qualified name.
*/
private static boolean propertyMustBeInitializedByFullName(Name name) {
// If an object literal
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
*
* public static void main(String[] args) {
* (new MyCommandLineRunner(args)).run();
* }
* }
* </pre>
*
* @author bolinfest@google.com (Michael Bolin)
*/
abstract class AbstractCommandLineRunner<A extends Compiler,
B extends CompilerOptions> {
private final CommandLineConfig config;
private Appendable out;
private final PrintStream err;
private A compiler;
private Charset inputCharset;
private String outputCharset;
private boolean testMode = false;
private Supplier<List<JSSourceFile>> externsSupplierForTesting = null;
private Supplier<List<JSSourceFile>> inputsSupplierForTesting = null;
private Supplier<List<JSModule>> modulesSupplierForTesting = null;
private Function<Integer, Boolean> exitCodeReceiverForTesting = null;
// Bookkeeping to measure optimal phase orderings.
private static final int NUM_RUNS_TO_DETERMINE_OPTIMAL_ORDER = 100;
private final RunTimeStats runTimeStats = new RunTimeStats();
AbstractCommandLineRunner() {
this(System.out, System.err);
}
AbstractCommandLineRunner(PrintStream out, PrintStream err) {
this.config = new CommandLineConfig();
this.out = out;
this.err = err;
}
/**
* Put the command line runner into test mode. In test mode,
* all outputs will be blackholed.
* @param externsSupplier A provider for externs.
* @param inputsSupplier A provider for source inputs.
* @param modulesSupplier A provider for modules. Only one of inputsSupplier
* and modulesSupplier may be non-null.
* @param exitCodeReceiver A receiver for the status code that would
* have been passed to System.exit in non-test mode.
*/
@VisibleForTesting
void enableTestMode(
Supplier<List<JSSourceFile>> externsSupplier,
Supplier<List<JSSourceFile>> inputsSupplier,
Supplier<List<JSModule>> modulesSupplier,
Function<Integer, Boolean> exitCodeReceiver) {
Preconditions.checkArgument(
inputsSupplier == null ^ modulesSupplier == null);
testMode = true;
this.externsSupplierForTesting = externsSupplier;
this.inputsSupplierForTesting = inputsSupplier;
this.modulesSupplierForTesting = modulesSupplier;
this.exitCodeReceiverForTesting = exitCodeReceiver;
}
/**
* Returns whether we're in test mode.
*/
protected boolean isInTestMode() {
return testMode;
}
/**
* Get the command line config, so that it can be initialized.
*/
protected CommandLineConfig getCommandLineConfig() {
return config;
}
/**
* Returns the instance of the Compiler to use when {@link #run()} is
* called.
*/
protected abstract A createCompiler();
/**
* Returns the instance of the Options to use when {@link #run()} is called.
* createCompiler() is called before createOptions(),
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>fromCode("/dev/null", ""));
}
try {
return createInputs(files, false);
} catch (FlagUsageException e) {
throw new FlagUsageException("Bad --externs flag. " + e.getMessage());
}
}
/**
* Creates module objects from a list of module specifications.
*
* @param specs A list of module specifications, not null or empty. The spec
* format is: <code>name:num-js-files[:[dep,...][:]]</code>. Module
* names must not contain the ':' character.
* @param jsFiles A list of js file paths, not null
* @return An array of module objects
*/
List<JSModule> createJsModules(
List<String> specs, List<String> jsFiles)
throws FlagUsageException, IOException {
if (isInTestMode()) {
return modulesSupplierForTesting.get();
}
Preconditions.checkState(specs != null);
Preconditions.checkState(!specs.isEmpty());
Preconditions.checkState(jsFiles != null);
final int totalNumJsFiles = jsFiles.size();
int nextJsFileIndex = 0;
Map<String, JSModule> modulesByName = Maps.newLinkedHashMap();
for (String spec : specs) {
// Format is "<name>:<num-js-files>[:[<dep>,...][:]]".
String[] parts = spec.split(":");
if (parts.length < 2 || parts.length > 4) {
throw new FlagUsageException("Expected 2-4 colon-delimited parts in "
+ "module spec: " + spec);
}
// Parse module name.
String name = parts[0];
if (!TokenStream.isJSIdentifier(name)) {
throw new FlagUsageException("Invalid module name: '" + name + "'");
}
if (modulesByName.containsKey(name)) {
throw new FlagUsageException("Duplicate module name: " + name);
}
JSModule module = new JSModule(name);
// Parse module inputs.
int numJsFiles = -1;
try {
numJsFiles = Integer.parseInt(parts[1]);
} catch (NumberFormatException ignored) {
numJsFiles = -1;
}
// We will allow modules of zero input.
if (numJsFiles < 0) {
throw new FlagUsageException("Invalid js file count '" + parts[1]
+ "' for module: " + name);
}
if (nextJsFileIndex + numJsFiles > totalNumJsFiles) {
throw new FlagUsageException("Not enough js files specified. Expected "
+ (nextJsFileIndex + numJsFiles - totalNumJsFiles)
+ " more in module:" + name);
}
List<String> moduleJsFiles =
jsFiles.subList(nextJsFileIndex, nextJsFileIndex + numJsFiles);
for (JSSourceFile input : createInputs(moduleJsFiles, false)) {
module.add(input);
}
nextJs
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>FileIndex += numJsFiles;
if (parts.length > 2) {
// Parse module dependencies.
String depList = parts[2];
if (depList.length() > 0) {
String[] deps = depList.split(",");
for (String dep : deps) {
JSModule other = modulesByName.get(dep);
if (other == null) {
throw new FlagUsageException("Module '" + name
+ "' depends on unknown module '" + dep
+ "'. Be sure to list modules in dependency order.");
}
module.addDependency(other);
}
}
}
modulesByName.put(name, module);
}
if (nextJsFileIndex < totalNumJsFiles) {
throw new FlagUsageException("Too many js files specified. Expected "
+ nextJsFileIndex + " but found " + totalNumJsFiles);
}
return Lists.newArrayList(modulesByName.values());
}
/**
* Parses module wrapper specifications.
*
* @param specs A list of module wrapper specifications, not null. The spec
* format is: <code>name:wrapper</code>. Wrappers.
* @param modules The JS modules whose wrappers are specified
* @return A map from module name to module wrapper. Modules with no wrapper
* will have the empty string as their value in this map.
*/
static Map<String, String> parseModuleWrappers(List<String> specs,
List<JSModule> modules) throws FlagUsageException {
Preconditions.checkState(specs != null);
Map<String, String> wrappers =
Maps.newHashMapWithExpectedSize(modules.size());
// Prepopulate the map with module names.
for (JSModule m : modules) {
wrappers.put(m.getName(), "");
}
for (String spec : specs) {
// Format is "<name>:<wrapper>".
int pos = spec.indexOf(':');
if (pos == -1) {
throw new FlagUsageException("Expected module wrapper to have "
+ "<name>:<wrapper> format: " + spec);
}
// Parse module name.
String name = spec.substring(0, pos);
if (!wrappers.containsKey(name)) {
throw new FlagUsageException("Unknown module: '" + name + "'");
}
String wrapper = spec.substring(pos + 1);
if (!wrapper.contains("%s")) {
throw new FlagUsageException("No %s placeholder in module wrapper: '"
+ wrapper + "'");
}
wrappers.put(name, wrapper);
}
return wrappers;
}
/**
* Writes code to an output stream, optionally wrapping it in an arbitrary
* wrapper that contains a placeholder where the code should be inserted.
*/
static void writeOutput(Appendable out, Compiler compiler, String code,
String wrapper, String codePlaceholder) throws IOException {
int pos = wrapper.indexOf(codePlaceholder);
if (pos != -1) {
String prefix = "";
if (pos > 0) {
prefix = wrapper.substring(0
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> this.sourceMapFormat = format;
return this;
}
private final List<String> jscompError = Lists.newArrayList();
/**
* Make the named class of warnings an error.
*/
CommandLineConfig setJscompError(List<String> jscompError) {
this.jscompError.clear();
this.jscompError.addAll(jscompError);
return this;
}
private final List<String> jscompWarning = Lists.newArrayList();
/**
* Make the named class of warnings a normal warning.
*/
CommandLineConfig setJscompWarning(List<String> jscompWarning) {
this.jscompWarning.clear();
this.jscompWarning.addAll(jscompWarning);
return this;
}
private final List<String> jscompOff = Lists.newArrayList();
/**
* Turn off the named class of warnings.
*/
CommandLineConfig setJscompOff(List<String> jscompOff) {
this.jscompOff.clear();
this.jscompOff.addAll(jscompOff);
return this;
}
private final List<String> define = Lists.newArrayList();
/**
* Override the value of a variable annotated @define.
* The format is <name>[=<val>], where <name> is the name of a @define
* variable and <val> is a boolean, number, or a single-quoted string
* that contains no single quotes. If [=<val>] is omitted,
* the variable is marked true
*/
CommandLineConfig setDefine(List<String> define) {
this.define.clear();
this.define.addAll(define);
return this;
}
private String charset = "";
/**
* Input charset for all files.
*/
CommandLineConfig setCharset(String charset) {
this.charset = charset;
return this;
}
private boolean manageClosureDependencies = false;
/**
* Sets whether to sort files by their goog.provide/require deps,
* and prune inputs that are not required.
*/
CommandLineConfig setManageClosureDependencies(boolean newVal) {
this.manageClosureDependencies = newVal;
return this;
}
private List<String> closureEntryPoints = ImmutableList.of();
/**
* Set closure entry points, which makes the compiler only include
* those files and sort them in dependency order.
*/
CommandLineConfig setClosureEntryPoints(List<String> entryPoints) {
Preconditions.checkNotNull(entryPoints);
this.closureEntryPoints = entryPoints;
return this;
}
private String outputManifest = "";
/**
* Sets whether to print an output manifest file.
*/
CommandLineConfig setOutputManifest(String outputManifest) {
this.outputManifest = outputManifest;
return this;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.error("JSC_GETCSSNAME_UNEXPECTED_STRING_LITERAL",
"goog.getCssName called with invalid arguments, string literal " +
"passed as first of two arguments. Did you mean " +
"goog.getCssName(\"{0}-{1}\")?");
static final DiagnosticType UNKNOWN_SYMBOL_WARNING =
DiagnosticType.warning("JSC_GETCSSNAME_UNKNOWN_CSS_SYMBOL",
"goog.getCssName called with unrecognized symbol \"{0}\" in class " +
"\"{1}\".");
private final AbstractCompiler compiler;
private final Map<String, Integer> cssNames;
private CssRenamingMap symbolMap;
private final JSType nativeStringType;
ReplaceCssNames(AbstractCompiler compiler,
@Nullable Map<String, Integer> cssNames) {
this.compiler = compiler;
this.cssNames = cssNames;
this.nativeStringType = compiler.getTypeRegistry()
.getNativeType(STRING_TYPE);
}
@Override
public void process(Node externs, Node root) {
// The CssRenamingMap may not have been available from the compiler when
// this ReplaceCssNames pass was constructed, so getCssRenamingMap() should
// only be called before this pass is actually run.
symbolMap = getCssRenamingMap();
NodeTraversal.traverse(compiler, root, new Traversal());
}
@VisibleForTesting
protected CssRenamingMap getCssRenamingMap() {
return compiler.getCssRenamingMap();
}
private class Traversal extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.CALL &&
GET_CSS_NAME_FUNCTION.equals(n.getFirstChild().getQualifiedName())) {
int count = n.getChildCount();
Node first = n.getFirstChild().getNext();
switch (count) {
case 2:
// Replace the function call with the processed argument.
if (first.getType() == Token.STRING) {
processStringNode(t, first);
n.removeChild(first);
parent.replaceChild(n, first);
compiler.reportCodeChange();
} else {
compiler.report(t.makeError(n, STRING_LITERAL_EXPECTED_ERROR,
Token.name(first.getType())));
}
break;
case 3:
// Replace function call with concatenation of two args. It's
// assumed the first arg has already been processed.
Node second = first.getNext();
if (first.getType() == Token.STRING) {
compiler.report(t.makeError(
n, UNEXPECTED_STRING_LITERAL_ERROR,
first.getString(), second.getString()));
} else if (second.getType() == Token.STRING) {
processStringNode(t, second);
n.removeChild(first);
Node replacement = new Node(Token.ADD, first,
Node.newString("-" + second.getString())
.copyInformationFrom(second))
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
.copyInformationFrom(n);
replacement.setJSType(nativeStringType);
parent.replaceChild(n, replacement);
compiler.reportCodeChange();
} else {
compiler.report(t.makeError(n, STRING_LITERAL_EXPECTED_ERROR,
Token.name(second.getType())));
}
break;
default:
compiler.report(t.makeError(
n, INVALID_NUM_ARGUMENTS_ERROR, String.valueOf(count)));
}
}
}
/**
* Processes a string argument to goog.getCssName(). The string will be
* renamed based off the symbol map. If there is no map or any part of the
* name can't be renamed, a warning is reported to the compiler and the node
* is left unchanged.
*
* If the type is unexpected then an error is reported to the compiler.
*
* @param t The node traversal.
* @param n The string node to process.
*/
private void processStringNode(NodeTraversal t, Node n) {
if (symbolMap != null || cssNames != null) {
String[] parts = n.getString().split("-");
for (int i = 0; i < parts.length; i++) {
if (cssNames != null) {
Integer count = cssNames.get(parts[i]);
if (count == null) {
count = Integer.valueOf(0);
}
cssNames.put(parts[i], count.intValue() + 1);
}
if (symbolMap != null) {
String replacement = symbolMap.get(parts[i]);
if (replacement == null) {
// If we can't encode all parts, don't encode any of it.
compiler.report(t.makeError(
n, UNKNOWN_SYMBOL_WARNING, parts[i], n.getString()));
return;
}
parts[i] = replacement;
}
}
if (symbolMap != null) {
n.setString(Joiner.on("-").join(parts));
}
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>$inherits(SubClass, SuperClass)
// SubClass.mixin(SuperClass.prototype)
// goog.mixin(SubClass.prototype, SuperClass.prototype)
// goog$mixin(SubClass.prototype, SuperClass.prototype)
if (callNode.getChildCount() == 2 &&
callName.getType() == Token.GETPROP) {
// SubClass.inherits(SuperClass)
subclass = callName.getFirstChild();
} else if (callNode.getChildCount() == 3) {
// goog.inherits(SubClass, SuperClass)
subclass = callName.getNext();
}
// bail out if either of the side of the "inherits"
// isn't a real class name. This prevents us from
// doing something weird in cases like:
// goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2)
if (subclass != null &&
subclass.isUnscopedQualifiedName() &&
superclass.isUnscopedQualifiedName()) {
// make sure to strip the prototype off of the nodes
// to normalize for goog.mixin
return new SubclassRelationship(
type,
stripPrototype(subclass),
stripPrototype(superclass));
}
}
return null;
}
/**
* Determines whether the given node is a class-defining name, like
* "inherits" or "mixin."
* @return The type of class-defining name, or null.
*/
private SubclassType typeofClassDefiningName(Node callName) {
// Check if the method name matches one of the class-defining methods.
String methodName = null;
if (callName.getType() == Token.GETPROP) {
methodName = callName.getLastChild().getString();
} else if (callName.getType() == Token.NAME) {
String name = callName.getString();
int dollarIndex = name.lastIndexOf('$');
if (dollarIndex != -1) {
methodName = name.substring(dollarIndex + 1);
}
}
if (methodName != null) {
if (methodName.equals("inherits")) {
return SubclassType.INHERITS;
} else if (methodName.equals("mixin")) {
return SubclassType.MIXIN;
}
}
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return "superClass_".equals(propertyName);
}
/**
* Given a qualified name node, strip "prototype" off the end.
*
* Examples of this transformation:
* a.b.c => a.b.c
* a.b.c.prototype => a.b.c
*/
private Node stripPrototype(Node qualifiedName) {
if (qualifiedName.getType() == Token.GETPROP &&
qualifiedName.getLastChild().getString().equals("prototype")) {
return qualifiedName.getFirstChild();
}
return qualifiedName;
}
/**
* Exctracts X from goog.provide('X'), if the applied Node is goog.
*
*
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Exctracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.getType() == Token.GETPROP) {
String qualifiedName = callee.getQualifiedName();
if ((functionName).equals(qualifiedName)) {
className = callee.getNext().getString();
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.getType() == Token.ARRAYLIT) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.getType() == Token.STRING) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return null;
}
@Override
public String identifyTypeDefAssign(Node n) {
Node firstChild = n.getFirstChild();
int type = n.getType();
if (type == Token.ASSIGN) {
if (TYPEDEF_NAME.equals(n.getLastChild().getQualifiedName())) {
return firstChild.getQualifiedName();
}
} else if (type == Token.VAR && firstChild.hasChildren()) {
if (TYPEDEF_NAME.equals(
firstChild.getFirstChild().getQualifiedName())) {
return firstChild.getString();
}
}
return null;
}
@Override
public String getAbstractMethodName() {
return "goog.abstractMethod";
}
@Override
public String getSingletonGetterClassName(Node callNode) {
Node callArg = callNode.getFirstChild();
String callName = callArg.get
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>QualifiedName();
// Use both the original name and the post-CollapseProperties name.
if (!("goog.addSingletonGetter".equals(callName) ||
"goog$addSingletonGetter".equals(callName)) ||
callNode.getChildCount() != 2) {
return null;
}
return callArg.getNext().getQualifiedName();
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
functionType.defineDeclaredProperty("getInstance", getterType, false);
functionType.defineDeclaredProperty("instance_", objectType, false);
}
@Override
public String getGlobalObject() {
return "goog.global";
}
private final Set<String> propertyTestFunctions = ImmutableSet.of(
"goog.isDef", "goog.isNull", "goog.isDefAndNotNull",
"goog.isString", "goog.isNumber", "goog.isBoolean",
"goog.isFunction", "goog.isArray", "goog.isObject");
@Override
public boolean isPropertyTestFunction(Node call) {
Preconditions.checkArgument(call.getType() == Token.CALL);
return propertyTestFunctions.contains(
call.getFirstChild().getQualifiedName());
}
@Override
public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t,
Node callNode) {
Preconditions.checkArgument(callNode.getType() == Token.CALL);
Node callName = callNode.getFirstChild();
if (!"goog.reflect.object".equals(callName.getQualifiedName()) ||
callName.getChildCount() != 2) {
return null;
}
Node typeNode = callName.getNext();
if (!typeNode.isQualifiedName()) {
return null;
}
Node objectNode = typeNode.getNext();
if (objectNode.getType() != Token.OBJECTLIT) {
t.getCompiler().report(JSError.make(t.getSourceName(), callNode,
OBJECTLIT_EXPECTED));
return null;
}
return new ObjectLiteralCast(typeNode.getQualifiedName(),
typeNode.getNext());
}
@Override
public boolean isOptionalParameter(Node parameter) {
return false;
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return false;
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return ImmutableList.<AssertionFunctionSpec>of(
new AssertionFunctionSpec("goog.asserts.assert"),
new AssertionFunctionSpec("goog.asserts.assertNumber",
JSTypeNative.NUMBER_TYPE),
new AssertionFunctionSpec("goog.asserts.assertString",
JSTypeNative.STRING_TYPE),
new AssertionFunctionSpec("goog.asserts.assertFunction",
JSTypeNative.FUNCTION_INSTANCE_TYPE),
new AssertionFunctionSpec("goog.asserts.assertObject",
JSTypeNative.OBJECT_TYPE),
new AssertionFunctionSpec("goog.asserts.assertArray
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
case FIXUPS_PROP: return "fixups";
case VARS_PROP: return "vars";
case USES_PROP: return "uses";
case REGEXP_PROP: return "regexp";
case CASES_PROP: return "cases";
case DEFAULT_PROP: return "default";
case CASEARRAY_PROP: return "casearray";
case SOURCENAME_PROP: return "sourcename";
case TYPE_PROP: return "type";
case SPECIAL_PROP_PROP: return "special_prop";
case LABEL_PROP: return "label";
case FINALLY_PROP: return "finally";
case LOCALCOUNT_PROP: return "localcount";
case TARGETBLOCK_PROP: return "targetblock";
case VARIABLE_PROP: return "variable";
case LASTUSE_PROP: return "lastuse";
case ISNUMBER_PROP: return "isnumber";
case DIRECTCALL_PROP: return "directcall";
case SPECIALCALL_PROP: return "specialcall";
case DEBUGSOURCE_PROP: return "debugsource";
case JSDOC_INFO_PROP: return "jsdoc_info";
case SKIP_INDEXES_PROP: return "skip_indexes";
case INCRDECR_PROP: return "incrdecr";
case MEMBER_TYPE_PROP: return "member_type";
case NAME_PROP: return "name";
case PARENTHESIZED_PROP: return "parenthesized";
case QUOTED_PROP: return "quoted";
case SYNTHETIC_BLOCK_PROP: return "synthetic";
case EMPTY_BLOCK: return "empty_block";
case ORIGINALNAME_PROP: return "originalname";
case SIDE_EFFECT_FLAGS: return "side_effect_flags";
case IS_CONSTANT_NAME: return "is_constant_name";
case IS_OPTIONAL_PARAM: return "is_optional_param";
case IS_VAR_ARGS_PARAM: return "is_var_args_param";
case IS_NAMESPACE: return "is_namespace";
case IS_DISPATCHER: return "is_dispatcher";
case DIRECTIVES: return "directives";
case DIRECT_EVAL: return "direct_eval";
case FREE_CALL: return "free_call";
default:
Kit.codeBug();
}
return null;
}
private static class NumberNode extends Node {
private static final long serialVersionUID = 1L;
NumberNode(double number) {
super(Token.NUMBER);
this.number = number;
}
public NumberNode(double number, int lineno, int charno) {
super(Token.NUMBER, lineno, charno);
this.number = number;
}
@Override
public double getDouble() {
return this.number;
}
@Override
public void setDouble(double d) {
this.number = d
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
}
@Override
public boolean isEquivalentTo(Node node) {
return (node instanceof NumberNode
&& getDouble() == ((NumberNode) node).getDouble());
}
private double number;
}
private static class StringNode extends Node {
private static final long serialVersionUID = 1L;
StringNode(int type, String str) {
super(type);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
StringNode(int type, String str, int lineno, int charno) {
super(type, lineno, charno);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
/**
* returns the string content.
* @return non null.
*/
@Override
public String getString() {
return this.str;
}
/**
* sets the string content.
* @param str the new value. Non null.
*/
@Override
public void setString(String str) {
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
@Override
public boolean isEquivalentTo(Node node) {
return (node instanceof StringNode &&
this.str.equals(((StringNode) node).str));
}
/**
* If the property is not defined, this was not a quoted key. The
* QUOTED_PROP int property is only assigned to STRING tokens used as
* object lit keys.
* @return true if this was a quoted string key in an object literal.
*/
@Override
public boolean isQuotedString() {
return getBooleanProp(QUOTED_PROP);
}
/**
* This should only be called for STRING nodes created in object lits.
*/
@Override
public void setQuotedString() {
putBooleanProp(QUOTED_PROP, true);
}
private String str;
}
// PropListItems are immutable so that they can be shared.
private static class PropListItem implements Serializable {
private static final long serialVersionUID = 1L;
final PropListItem next;
final int type;
final int intValue;
final Object objectValue;
PropListItem(int type, int intValue, PropListItem next) {
this(type, intValue, null, next);
}
PropListItem(int type, Object objectValue, PropListItem next) {
this(type, 0, objectValue, next);
}
PropListItem(
int type, int intValue, Object objectValue, PropListItem next) {
this.type = type;
this.intValue = intValue;
this.objectValue = objectValue;
this.next = next;
}
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type = nodeType;
parent = null;
first = last = child;
child.next = null;
child.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node right) {
Preconditions.checkArgument(left.parent == null,
"first new child has existing parent");
Preconditions.checkArgument(left.next == null,
"first new child has existing sibling");
Preconditions.checkArgument(right.parent == null,
"second new child has existing parent");
Preconditions.checkArgument(right.next == null,
"second new child has existing sibling");
type = nodeType;
parent = null;
first = left;
last = right;
left.next = right;
left.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = right;
mid.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(mid2.parent == null);
Preconditions.checkArgument(mid2.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = mid2;
mid.parent = this;
mid2.next = right;
mid2.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, int lineno, int charno) {
type = nodeType;
parent = null;
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node child, int lineno, int charno) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
this(nodeType, child);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node right, int lineno, int charno) {
this(nodeType, left, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node right,
int lineno, int charno) {
this(nodeType, left, mid, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right,
int lineno, int charno) {
this(nodeType, left, mid, mid2, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children, int lineno, int charno) {
this(nodeType, children);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children) {
this.type = nodeType;
parent = null;
if (children.length != 0) {
this.first = children[0];
this.last = children[children.length - 1];
for (int i = 1; i < children.length; i++) {
if (null != children[i - 1].next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> }
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first) {
return null;
}
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null) {
throw new RuntimeException("node is not a child");
}
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while (i > 0) {
n = n.next;
i--;
}
return n;
}
public Node getLastSibling() {
Node n = this;
while (n.next != null) {
n = n.next;
}
return n;
}
public void addChildToFront(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = first;
first = child;
if (last == null) {
last = child;
}
}
public void addChildToBack(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = null;
if (last == null) {
first = last = child;
return;
}
last.next = child;
last = child;
}
public void addChildrenToFront(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
Node lastSib = children.getLastSibling();
lastSib.next = first;
first = children;
if (last == null) {
last = lastSib;
}
}
public void addChildrenToBack(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
if (last != null) {
last.next = children;
}
last = children.getLastSibling();
if (first == null) {
first = children;
}
}
/**
* Add 'child' before 'node'.
*/
public void addChildBefore(Node newChild, Node node) {
Preconditions.checkArgument(node != null,
"The existing child node of the parent should not be null.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
if (first == node) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> newChild.parent = this;
newChild.next = first;
first = newChild;
return;
}
Node prev = getChildBefore(node);
addChildAfter(newChild, prev);
}
/**
* Add 'child' after 'node'.
*/
public void addChildAfter(Node newChild, Node node) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
newChild.parent = this;
newChild.next = node.next;
node.next = newChild;
if (last == node) {
last = newChild;
}
}
/**
* Detach a child from its parent and siblings.
*/
public void removeChild(Node child) {
Node prev = getChildBefore(child);
if (prev == null)
first = first.next;
else
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
}
/**
* Detaches child from Node and replaces it with newChild.
*/
public void replaceChild(Node child, Node newChild) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(child);
newChild.next = child.next;
newChild.parent = this;
if (child == first) {
first = newChild;
} else {
Node prev = getChildBefore(child);
prev.next = newChild;
}
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
public void replaceChildAfter(Node prevChild, Node newChild) {
Preconditions.checkArgument(prevChild.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(prevChild);
Node child = prevChild.next;
newChild.next = child.next;
newChild.parent = this;
prevChild.next = newChild;
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
@VisibleForTesting
PropListItem lookupProperty(int propType) {
PropListItem x = propListHead;
while (x != null && propType != x.type) {
x = x.next;
}
return x;
}
/**
* Clone the properties
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> from the provided node without copying
* the property object. The recieving node may not have any
* existing properties.
* @param other The node to clone properties from.
* @return this node.
*/
public Node clonePropsFrom(Node other) {
Preconditions.checkState(this.propListHead == null,
"Node has existing properties.");
this.propListHead = other.propListHead;
return this;
}
public void removeProp(int propType) {
PropListItem result = removeProp(propListHead, propType);
if (result != propListHead) {
propListHead = result;
}
}
/**
* @param item The item to inspect
* @param propType The property to look for
* @return The replacement list if the property was removed, or
* 'item' otherwise.
*/
private PropListItem removeProp(PropListItem item, int propType) {
if (item == null) {
return null;
} else if (item.type == propType) {
return item.next;
} else {
PropListItem result = removeProp(item.next, propType);
if (result != item.next) {
return new PropListItem(
item.type, item.intValue, item.objectValue, result);
} else {
return item;
}
}
}
public Object getProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return null;
}
return item.objectValue;
}
public boolean getBooleanProp(int propType) {
return getIntProp(propType) != 0;
}
/**
* Returns the integer value for the property, or 0 if the property
* is not defined.
*/
public int getIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return 0;
}
return item.intValue;
}
public int getExistingIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
Kit.codeBug();
}
return item.intValue;
}
public void putProp(int propType, Object value) {
removeProp(propType);
if (value != null) {
propListHead = new PropListItem(propType, value, propListHead);
}
}
public void putBooleanProp(int propType, boolean value) {
putIntProp(propType, value ? 1 : 0);
}
public void putIntProp(int propType, int value) {
removeProp(propType);
if (value != 0) {
propListHead = new PropListItem(propType, value, propListHead);
}
}
// Gets all the property types, in sorted order.
private int[] getSortedPropTypes() {
int count = 0;
for (PropListItem x = prop
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>ListHead; x != null; x = x.next) {
count++;
}
int[] keys = new int[count];
for (PropListItem x = propListHead; x != null; x = x.next) {
count--;
keys[count] = x.type;
}
Arrays.sort(keys);
return keys;
}
public int getLineno() {
return extractLineno(sourcePosition);
}
public int getCharno() {
return extractCharno(sourcePosition);
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public double getDouble() throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a number node");
}
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public void setDouble(double s) throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public String getString() throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public void setString(String s) throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
@Override
public String toString() {
return toString(true, true, true);
}
public String toString(
boolean printSource,
boolean printAnnotations,
boolean printType) {
if (Token.printTrees) {
StringBuilder sb = new StringBuilder();
toString(sb, printSource, printAnnotations, printType);
return sb.toString();
}
return String.valueOf(type);
}
private void toString(
StringBuilder sb,
boolean printSource,
boolean printAnnotations,
boolean printType) {
if (Token.printTrees) {
sb.append(Token.name(type));
if (this instanceof StringNode) {
sb.append(' ');
sb.append(getString());
} else if (type == Token.FUNCTION) {
sb.append(' ');
// In the case of JsDoc trees, the first child is often not a string
// which causes exceptions to be thrown when calling toString or
// toStringTree.
if (first.getType() == Token.STRING) {
sb.append(first.getString());
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> } else if (this instanceof ScriptOrFnNode) {
ScriptOrFnNode sof = (ScriptOrFnNode) this;
if (this instanceof FunctionNode) {
FunctionNode fn = (FunctionNode) this;
sb.append(' ');
sb.append(fn.getFunctionName());
}
if (printSource) {
sb.append(" [source name: ");
sb.append(sof.getSourceName());
sb.append("] [encoded source length: ");
sb.append(sof.getEncodedSourceEnd() - sof.getEncodedSourceStart());
sb.append("] [base line: ");
sb.append(sof.getBaseLineno());
sb.append("] [end line: ");
sb.append(sof.getEndLineno());
sb.append(']');
}
} else if (type == Token.NUMBER) {
sb.append(' ');
sb.append(getDouble());
}
if (printSource) {
int lineno = getLineno();
if (lineno != -1) {
sb.append(' ');
sb.append(lineno);
}
}
if (printAnnotations) {
int[] keys = getSortedPropTypes();
for (int i = 0; i < keys.length; i++) {
int type = keys[i];
PropListItem x = lookupProperty(type);
sb.append(" [");
sb.append(propToString(type));
sb.append(": ");
String value;
switch (type) {
case TARGETBLOCK_PROP: // can't add this as it recurses
value = "target block property";
break;
case LOCAL_BLOCK_PROP: // can't add this as it is dull
value = "last local block";
break;
case ISNUMBER_PROP:
switch (x.intValue) {
case BOTH:
value = "both";
break;
case RIGHT:
value = "right";
break;
case LEFT:
value = "left";
break;
default:
throw Kit.codeBug();
}
break;
case SPECIALCALL_PROP:
switch (x.intValue) {
case SPECIALCALL_EVAL:
value = "eval";
break;
case SPECIALCALL_WITH:
value = "with";
break;
default:
// NON_SPECIALCALL should not be stored
throw Kit.codeBug();
}
break;
default:
Object obj = x.objectValue;
if (obj != null) {
value = obj.toString();
} else {
value = String.valueOf(x.intValue);
}
break;
}
sb.append(value);
sb.append(']');
}
}
if (printType) {
if (jsType != null) {
String jsTypeString = jsType.toString();
if (jsTypeString != null) {
sb.append(" : ");
sb.append(jsTypeString);
}
}
}
}
}
public String toStringTree() {
return toStringTreeImpl();
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> }
private String toStringTreeImpl() {
try {
StringBuilder s = new StringBuilder();
appendStringTree(s);
return s.toString();
} catch (IOException e) {
throw new RuntimeException("Should not happen\n" + e);
}
}
public void appendStringTree(Appendable appendable) throws IOException {
toStringTreeHelper(this, 0, appendable);
}
private static void toStringTreeHelper(Node n, int level, Appendable sb)
throws IOException {
if (Token.printTrees) {
for (int i = 0; i != level; ++i) {
sb.append(" ");
}
sb.append(n.toString());
sb.append('\n');
for (Node cursor = n.getFirstChild();
cursor != null;
cursor = cursor.getNext()) {
toStringTreeHelper(cursor, level + 1, sb);
}
}
}
int type; // type of the node; Token.NAME for example
Node next; // next sibling
private Node first; // first element of a linked list of children
private Node last; // last element of a linked list of children
/**
* Linked list of properties. Since vast majority of nodes would have
* no more then 2 properties, linked list saves memory and provides
* fast lookup. If this does not holds, propListHead can be replaced
* by UintMap.
*/
private PropListItem propListHead;
/**
* COLUMN_BITS represents how many of the lower-order bits of
* sourcePosition are reserved for storing the column number.
* Bits above these store the line number.
* This gives us decent position information for everything except
* files already passed through a minimizer, where lines might
* be longer than 4096 characters.
*/
public static final int COLUMN_BITS = 12;
/**
* MAX_COLUMN_NUMBER represents the maximum column number that can
* be represented. JSCompiler's modifications to Rhino cause all
* tokens located beyond the maximum column to MAX_COLUMN_NUMBER.
*/
public static final int MAX_COLUMN_NUMBER = (1 << COLUMN_BITS) - 1;
/**
* COLUMN_MASK stores a value where bits storing the column number
* are set, and bits storing the line are not set. It's handy for
* separating column number from line number.
*/
public static final int COLUMN_MASK = MAX_COLUMN_NUMBER;
/**
* Source position of this node. The position is encoded with the
* column number in the low 12 bits of the integer, and the line
* number in the rest. Create some handy constants so we can change this
* size if we want.
*/
private int sourcePosition;
private JSType jsType;
private Node parent;
//==========================================================================
// Source position management
public void setLineno(int lineno) {
int charno = getCharno();
if (charno == -1) {
charno =
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>NodeIterable
implements Iterable<Node>, Iterator<Node> {
private final Node start;
private Node current;
private boolean used;
SiblingNodeIterable(Node start) {
this.start = start;
this.current = start;
this.used = false;
}
public Iterator<Node> iterator() {
if (!used) {
used = true;
return this;
} else {
// We have already used the current object as an iterator;
// we must create a new SiblingNodeIterable based on this
// iterable's start node.
//
// Since the primary use case for Node.children is in for
// loops, this branch is extremely unlikely.
return (new SiblingNodeIterable(start)).iterator();
}
}
public boolean hasNext() {
return current != null;
}
public Node next() {
if (current == null) {
throw new NoSuchElementException();
}
try {
return current;
} finally {
current = current.getNext();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
// ==========================================================================
// Accessors
public Node getParent() {
return parent;
}
/**
* Gets the ancestor node relative to this.
*
* @param level 0 = this, 1 = the parent, etc.
*/
public Node getAncestor(int level) {
Preconditions.checkArgument(level >= 0);
Node node = this;
while (node != null && level-- > 0) {
node = node.getParent();
}
return node;
}
/**
* Iterates all of the node's ancestors excluding itself.
*/
public AncestorIterable getAncestors() {
return new AncestorIterable(this.getParent());
}
/**
* Iterator to go up the ancestor tree.
*/
public static class AncestorIterable implements Iterable<Node> {
private Node cur;
/**
* @param cur The node to start.
*/
AncestorIterable(Node cur) {
this.cur = cur;
}
public Iterator<Node> iterator() {
return new Iterator<Node>() {
public boolean hasNext() {
return cur != null;
}
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
Node n = cur;
cur = cur.getParent();
return n;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Check for one child more efficiently than by iterating over all the
* children as is done with Node.getChildCount().
*
* @return Whether the node has exactly one child.
*/
public boolean hasOneChild() {
return first != null && first == last;
}
/**
* Check for more than one child more efficiently than by iterating over all
* the children as is done with Node.getChildCount().
*
* @return Whether the node more than one child.
*/
public boolean hasMoreThanOneChild() {
return first != null
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> && first != last;
}
public int getChildCount() {
int c = 0;
for (Node n = first; n != null; n = n.next)
c++;
return c;
}
// Intended for testing and verification only.
public boolean hasChild(Node child) {
for (Node n = first; n != null; n = n.getNext()) {
if (child == n) {
return true;
}
}
return false;
}
/**
* Checks if the subtree under this node is the same as another subtree.
* Returns null if it's equal, or a message describing the differences.
*/
public String checkTreeEquals(Node node2) {
NodeMismatch diff = checkTreeEqualsImpl(node2);
if (diff != null) {
return "Node tree inequality:" +
"\nTree1:\n" + toStringTree() +
"\n\nTree2:\n" + node2.toStringTree() +
"\n\nSubtree1: " + diff.nodeA.toStringTree() +
"\n\nSubtree2: " + diff.nodeB.toStringTree();
}
return null;
}
/**
* If this is a compilation pass and not a test, do not construct error
* strings. Instead return true if the trees are equal.
*/
public boolean checkTreeEqualsSilent(Node node2) {
return checkTreeEqualsImpl(node2) == null;
}
/**
* Helper function to ignore differences in Node subclasses that are no longer
* used.
*/
@SuppressWarnings("unchecked")
static private Class getNodeClass(Node n) {
Class c = n.getClass();
if (c == FunctionNode.class || c == ScriptOrFnNode.class) {
return Node.class;
}
return c;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeEqualsImpl(Node node2) {
boolean eq = false;
if (type == node2.getType() && getChildCount() == node2.getChildCount()
&& getNodeClass(this) == getNodeClass(node2)) {
eq = this.isEquivalentTo(node2);
}
if (!eq) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTreeEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
/**
* Checks if the subtree under this node is the same as another subtree
* including types. Returns null if it's equal, or a message describing
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> the
* differences.
*/
public boolean checkTreeTypeAwareEqualsSilent(Node node2) {
return checkTreeTypeAwareEqualsImpl(node2) == null;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeTypeAwareEqualsImpl(Node node2) {
boolean eq = false;
if (type == node2.getType()
&& getChildCount() == node2.getChildCount()
&& getClass() == node2.getClass()
&& JSType.isEquivalent(jsType, node2.getJSType())) {
eq = this.isEquivalentTo(node2);
}
if (!eq) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTreeTypeAwareEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
public static String tokenToName(int token) {
switch (token) {
case Token.ERROR: return "error";
case Token.EOF: return "eof";
case Token.EOL: return "eol";
case Token.ENTERWITH: return "enterwith";
case Token.LEAVEWITH: return "leavewith";
case Token.RETURN: return "return";
case Token.GOTO: return "goto";
case Token.IFEQ: return "ifeq";
case Token.IFNE: return "ifne";
case Token.SETNAME: return "setname";
case Token.BITOR: return "bitor";
case Token.BITXOR: return "bitxor";
case Token.BITAND: return "bitand";
case Token.EQ: return "eq";
case Token.NE: return "ne";
case Token.LT: return "lt";
case Token.LE: return "le";
case Token.GT: return "gt";
case Token.GE: return "ge";
case Token.LSH: return "lsh";
case Token.RSH: return "rsh";
case Token.URSH: return "ursh";
case Token.ADD: return "add";
case Token.SUB: return "sub";
case Token.MUL: return "mul";
case Token.DIV: return "div";
case Token.MOD: return "mod";
case Token.BITNOT: return "bitnot";
case Token.NEG: return "neg";
case Token.NEW: return "new";
case Token.DELPROP: return "del
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>prop";
case Token.TYPEOF: return "typeof";
case Token.GETPROP: return "getprop";
case Token.SETPROP: return "setprop";
case Token.GETELEM: return "getelem";
case Token.SETELEM: return "setelem";
case Token.CALL: return "call";
case Token.NAME: return "name";
case Token.NUMBER: return "number";
case Token.STRING: return "string";
case Token.NULL: return "null";
case Token.THIS: return "this";
case Token.FALSE: return "false";
case Token.TRUE: return "true";
case Token.SHEQ: return "sheq";
case Token.SHNE: return "shne";
case Token.REGEXP: return "regexp";
case Token.POS: return "pos";
case Token.BINDNAME: return "bindname";
case Token.THROW: return "throw";
case Token.IN: return "in";
case Token.INSTANCEOF: return "instanceof";
case Token.GETVAR: return "getvar";
case Token.SETVAR: return "setvar";
case Token.TRY: return "try";
case Token.TYPEOFNAME: return "typeofname";
case Token.THISFN: return "thisfn";
case Token.SEMI: return "semi";
case Token.LB: return "lb";
case Token.RB: return "rb";
case Token.LC: return "lc";
case Token.RC: return "rc";
case Token.LP: return "lp";
case Token.RP: return "rp";
case Token.COMMA: return "comma";
case Token.ASSIGN: return "assign";
case Token.ASSIGN_BITOR: return "assign_bitor";
case Token.ASSIGN_BITXOR: return "assign_bitxor";
case Token.ASSIGN_BITAND: return "assign_bitand";
case Token.ASSIGN_LSH: return "assign_lsh";
case Token.ASSIGN_RSH: return "assign_rsh";
case Token.ASSIGN_URSH: return "assign_ursh";
case Token.ASSIGN_ADD: return "assign_add";
case Token.ASSIGN_SUB: return "assign_sub";
case Token.ASSIGN_MUL: return "assign_mul";
case Token.ASSIGN_DIV: return "assign_div";
case Token.ASSIGN_MOD: return "assign_mod";
case Token.HOOK: return "hook";
case Token.COLON: return "colon";
case Token.OR: return "or";
case Token.AND: return "and";
case Token.INC: return "inc";
case Token.DEC: return "dec";
case Token.DOT: return "
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>dot";
case Token.FUNCTION: return "function";
case Token.EXPORT: return "export";
case Token.IMPORT: return "import";
case Token.IF: return "if";
case Token.ELSE: return "else";
case Token.SWITCH: return "switch";
case Token.CASE: return "case";
case Token.DEFAULT: return "default";
case Token.WHILE: return "while";
case Token.DO: return "do";
case Token.FOR: return "for";
case Token.BREAK: return "break";
case Token.CONTINUE: return "continue";
case Token.VAR: return "var";
case Token.WITH: return "with";
case Token.CATCH: return "catch";
case Token.FINALLY: return "finally";
case Token.RESERVED: return "reserved";
case Token.NOT: return "not";
case Token.VOID: return "void";
case Token.BLOCK: return "block";
case Token.ARRAYLIT: return "arraylit";
case Token.OBJECTLIT: return "objectlit";
case Token.LABEL: return "label";
case Token.TARGET: return "target";
case Token.LOOP: return "loop";
case Token.EXPR_VOID: return "expr_void";
case Token.EXPR_RESULT: return "expr_result";
case Token.JSR: return "jsr";
case Token.SCRIPT: return "script";
case Token.EMPTY: return "empty";
case Token.GET_REF: return "get_ref";
case Token.REF_SPECIAL: return "ref_special";
}
return "<unknown="+token+">";
}
/** Returns true if this node is equivalent semantically to another */
public boolean isEquivalentTo(Node node) {
if (type == Token.ARRAYLIT) {
try {
int[] indices1 = (int[]) getProp(Node.SKIP_INDEXES_PROP);
int[] indices2 = (int[]) node.getProp(Node.SKIP_INDEXES_PROP);
if (indices1 == null) {
if (indices2 != null) {
return false;
}
} else if (indices2 == null) {
return false;
} else if (indices1.length != indices2.length) {
return false;
} else {
for (int i = 0; i < indices1.length; i++) {
if (indices1[i] != indices2[i]) {
return false;
}
}
}
} catch (Exception e) {
return false;
}
} else if (type == Token.INC || type == Token.DEC) {
int post1 = this.getIntProp(INCRDECR_PROP);
int post2 = node.getIntProp(INCRDECR_PROP);
if (post1 != post2) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> return false;
}
} else if (type == Token.STRING) {
int quoted1 = this.getIntProp(QUOTED_PROP);
int quoted2 = node.getIntProp(QUOTED_PROP);
if (quoted1 != quoted2) {
return false;
}
}
return true;
}
public boolean hasSideEffects() {
switch (type) {
case Token.EXPR_VOID:
case Token.COMMA:
if (last != null)
return last.hasSideEffects();
else
return true;
case Token.HOOK:
if (first == null || first.next == null || first.next.next == null) {
Kit.codeBug();
}
return first.next.hasSideEffects() && first.next.next.hasSideEffects();
case Token.ERROR: // Avoid cascaded error messages
case Token.EXPR_RESULT:
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ENTERWITH:
case Token.LEAVEWITH:
case Token.RETURN:
case Token.GOTO:
case Token.IFEQ:
case Token.IFNE:
case Token.NEW:
case Token.DELPROP:
case Token.SETNAME:
case Token.SETPROP:
case Token.SETELEM:
case Token.CALL:
case Token.THROW:
case Token.RETHROW:
case Token.SETVAR:
case Token.CATCH_SCOPE:
case Token.RETURN_RESULT:
case Token.SET_REF:
case Token.DEL_REF:
case Token.REF_CALL:
case Token.TRY:
case Token.SEMI:
case Token.INC:
case Token.DEC:
case Token.EXPORT:
case Token.IMPORT:
case Token.IF:
case Token.ELSE:
case Token.SWITCH:
case Token.WHILE:
case Token.DO:
case Token.FOR:
case Token.BREAK:
case Token.CONTINUE:
case Token.VAR:
case Token.CONST:
case Token.WITH:
case Token.CATCH:
case Token.FINALLY:
case Token.BLOCK:
case Token.LABEL:
case Token.TARGET:
case Token.LOOP:
case Token.JSR:
case Token.SETPROP_OP:
case Token.SETELEM_OP:
case Token.LOCAL_BLOCK:
case Token.SET_REF_OP:
return true;
default:
return false;
}
}
/**
* This function takes a set of GETPROP nodes and produces a string that is
* each property separated by dots. If the node
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
return getString();
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null;) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last =
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(prev.next != null,
"no next sibling.");
Node child = prev.next;
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
return child;
}
/**
* @return A detached clone of the Node, specifically excluding its children.
*/
public Node cloneNode() {
Node result;
try {
result = (Node) super.clone();
// PropListItem lists are immutable and can be shared so there is no
// need to clone them here.
result.next = null;
result.first = null;
result.last = null;
result.parent = null;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e.getMessage());
}
return result;
}
/**
* @return A detached clone of the Node and all its children.
*/
public Node cloneTree() {
Node result = cloneNode();
for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) {
Node n2clone = n2.cloneTree();
n2clone.parent = result;
if (result.last != null) {
result.last.next = n2clone;
}
if (result.first == null) {
result.first = n2clone;
}
result.last = n2clone;
}
return result;
}
/**
* Copies source file and name information from the other
* node given to the current node. Used for maintaining
* debug information across node append and remove operations.
* @return this
*/
public Node copyInformationFrom(Node other) {
if (getProp(ORIGINALNAME_PROP) == null) {
putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP));
}
if (getProp(SOURCENAME_PROP) == null) {
putProp(SOURCENAME_PROP, other.getProp(SOURCENAME_PROP));
sourcePosition = other.sourcePosition;
}
return this;
}
/**
* Copies source file and name information from the other node to the
* entire tree rooted at this node.
* @return this
*/
public Node copyInformationFromForTree(Node other) {
copyInformationFrom(other);
for (Node child = getFirstChild();
child != null; child = child.getNext()) {
child.copyInformationFromForTree(other);
}
return this;
}
//==========================================================================
// Custom annotations
public JSType getJSType() {
return jsType;
}
public void setJSType(JSType jsType) {
this.jsType = jsType;
}
public FileLevelJsDocBuilder get
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> flags) {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
"setIsNoSideEffectsCall only supports CALL and NEW nodes, got " +
Token.name(getType()));
putIntProp(SIDE_EFFECT_FLAGS, flags);
}
public void setSideEffectFlags(SideEffectFlags flags) {
setSideEffectFlags(flags.valueOf());
}
/**
* Returns the side effects flags for this node.
*/
public int getSideEffectFlags() {
return getIntProp(SIDE_EFFECT_FLAGS);
}
/**
* A helper class for getting and setting the side-effect flags.
* @author johnlenz@google.com (John Lenz)
*/
public static class SideEffectFlags {
private int value = Node.SIDE_EFFECTS_ALL;
public SideEffectFlags() {
}
public SideEffectFlags(int value) {
this.value = value;
}
public int valueOf() {
return value;
}
/** All side-effect occur and the returned results are non-local. */
public void setAllFlags() {
value = Node.SIDE_EFFECTS_ALL;
}
/** No side-effects occur and the returned results are local. */
public void clearAllFlags() {
value = Node.NO_SIDE_EFFECTS | Node.FLAG_LOCAL_RESULTS;
}
public boolean areAllFlagsSet() {
return value == Node.SIDE_EFFECTS_ALL;
}
/**
* Preserve the return result flag, but clear the others:
* no global state change, no throws, no this change, no arguments change
*/
public void clearSideEffectFlags() {
value |= Node.NO_SIDE_EFFECTS;
}
public void setMutatesGlobalState() {
// Modify global means everything must be assumed to be modified.
removeFlag(Node.FLAG_GLOBAL_STATE_UNMODIFIED);
removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED);
removeFlag(Node.FLAG_THIS_UNMODIFIED);
}
public void setThrows() {
removeFlag(Node.FLAG_NO_THROWS);
}
public void setMutatesThis() {
removeFlag(Node.FLAG_THIS_UNMODIFIED);
}
public void setMutatesArguments() {
removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED);
}
public void setReturnsTainted() {
removeFlag(Node.FLAG_LOCAL_RESULTS);
}
private void removeFlag(int flag) {
value &= ~flag;
}
}
/**
* @return Whether the only side-effect is "modifies this"
*/
public boolean isOnlyModifiesThisCall() {
return areBitFlagsSet(
getSideEffectFlags() & Node.NO_SIDE_EFFECTS,
Node.FLAG_GLOBAL_STATE_UNMODIFIED
| Node.FLAG_ARGUMENTS_UNMODIFIED
| Node.FLAG_NO_THROWS);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.common.base.Preconditions;
/**
* An object type that is an instance of some function constructor.
*/
public final class InstanceObjectType extends PrototypeObjectType {
private static final long serialVersionUID = 1L;
private final FunctionType constructor;
InstanceObjectType(JSTypeRegistry registry, FunctionType constructor) {
this(registry, constructor, false);
}
InstanceObjectType(JSTypeRegistry registry, FunctionType constructor,
boolean isNativeType) {
super(registry, null, null, isNativeType);
Preconditions.checkNotNull(constructor);
this.constructor = constructor;
}
@Override
public String getReferenceName() {
return getConstructor().getReferenceName();
}
@Override
public boolean hasReferenceName() {
return getConstructor().hasReferenceName();
}
@Override
public ObjectType getImplicitPrototype() {
return getConstructor().getPrototype();
}
@Override
public FunctionType getConstructor() {
return constructor;
}
@Override
boolean defineProperty(String name, JSType type, boolean inferred,
boolean inExterns) {
ObjectType proto = getImplicitPrototype();
if (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Controls.VISIBILITY_MISMATCH));
public static DiagnosticGroup NON_STANDARD_JSDOC =
DiagnosticGroups.registerGroup("nonStandardJsDocs",
new DiagnosticGroup(RhinoErrorReporter.BAD_JSDOC_ANNOTATION));
public static DiagnosticGroup ACCESS_CONTROLS =
DiagnosticGroups.registerGroup("accessControls",
new DiagnosticGroup(DEPRECATED, VISIBILITY));
public static DiagnosticGroup INVALID_CASTS = DiagnosticGroups
.registerGroup("invalidCasts",
new DiagnosticGroup(TypeValidator.INVALID_CAST));
public static DiagnosticGroup FILEOVERVIEW_JSDOC =
DiagnosticGroups.registerGroup("fileoverviewTags",
new DiagnosticGroup(RhinoErrorReporter.EXTRA_FILEOVERVIEW));
public static DiagnosticGroup STRICT_MODULE_DEP_CHECK =
DiagnosticGroups.registerGroup("strictModuleDepCheck",
new DiagnosticGroup(VarCheck.STRICT_MODULE_DEP_ERROR,
CheckGlobalNames.STRICT_MODULE_DEP_QNAME));
public static DiagnosticGroup EXTERNS_VALIDATION =
DiagnosticGroups.registerGroup("externsValidation",
new DiagnosticGroup(VarCheck.NAME_REFERENCE_IN_EXTERNS_ERROR,
VarCheck.UNDEFINED_EXTERN_VAR_ERROR));
public static DiagnosticGroup AMBIGUOUS_FUNCTION_DECL =
DiagnosticGroups.registerGroup("ambiguousFunctionDecl",
new DiagnosticGroup(VariableReferenceCheck.AMBIGUOUS_FUNCTION_DECL));
public static DiagnosticGroup UNKNOWN_DEFINES =
DiagnosticGroups.registerGroup("unknownDefines",
new DiagnosticGroup(ProcessDefines.UNKNOWN_DEFINE_WARNING));
public static DiagnosticGroup MISSING_PROPERTIES =
DiagnosticGroups.registerGroup("missingProperties",
new DiagnosticGroup(TypeCheck.INEXISTENT_PROPERTY));
public static DiagnosticGroup UNDEFINED_VARIABLES =
DiagnosticGroups.registerGroup("undefinedVars",
new DiagnosticGroup(VarCheck.UNDEFINED_VAR_ERROR));
public static DiagnosticGroup CHECK_REGEXP =
DiagnosticGroups.registerGroup("checkRegExp",
new DiagnosticGroup(
CheckRegExp.REGEXP_REFERENCE));
public static DiagnosticGroup CHECK_TYPES =
DiagnosticGroups.registerGroup("checkTypes",
new DiagnosticGroup(
TypeValidator.ALL_DIAGNOSTICS,
TypeCheck.ALL_DIAGNOSTICS));
public static DiagnosticGroup CHECK_VARIABLES =
DiagnosticGroups.registerGroup("checkVars",
new DiagnosticGroup(
VarCheck.UNDEFINED_VAR_ERROR,
SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR));
public static DiagnosticGroup CHECK_USELESS_CODE =
DiagnosticGroups.registerGroup("uselessCode",
new DiagnosticGroup(
CheckSideEffects.USELESS_CODE_ERROR,
CheckUnreachableCode.UNREACHABLE_CODE));
/**
* Adds warning levels by name.
*/
void setWarningLevels(CompilerOptions options,
List<String> diagnosticGroups, CheckLevel level) {
for (String name : diagnosticGroups) {
DiagnosticGroup group = forName(name);
Preconditions.checkNotNull(group, "No warning class for name: " + name);
options.set
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
* pass introduces
*/
OptimizeArgumentsArray(AbstractCompiler compiler, String paramPrefix) {
this.compiler = Preconditions.checkNotNull(compiler);
this.paramPredix = Preconditions.checkNotNull(paramPrefix);
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, Preconditions.checkNotNull(root), this);
}
@Override
public void enterScope(NodeTraversal traversal) {
Preconditions.checkNotNull(traversal);
// This optimization is valid only within a function so we are going to
// skip over the initial entry to the global scope.
Node function = traversal.getScopeRoot();
if (!NodeUtil.isFunction(function)) {
return;
}
// Introduces a new access list and stores the access list of the outer
// scope in the stack if necessary.
if (currentArgumentsAccess != null) {
argumentsAccessStack.push(currentArgumentsAccess);
}
currentArgumentsAccess = Lists.newLinkedList();
}
@Override
public void exitScope(NodeTraversal traversal) {
Preconditions.checkNotNull(traversal);
// This is the case when we are exiting the global scope where we had never
// collected argument access list. Since we do not perform this optimization
// for the global scope, we will skip this exit point.
if (currentArgumentsAccess == null) {
return;
}
// Attempt to replace the argument access and if the AST has been change,
// report back to the compiler.
if (tryReplaceArguments(traversal.getScope())) {
traversal.getCompiler().reportCodeChange();
}
// After the attempt to replace the arguments. The currentArgumentsAccess
// is stale and as we exit the Scope, no longer holds all the access to the
// current scope anymore. We'll pop the access list from the outer scope
// and set it as currentArgumentsAcess if the outer scope is not the global
// scope.
if (!argumentsAccessStack.isEmpty()) {
currentArgumentsAccess = argumentsAccessStack.pop();
} else {
currentArgumentsAccess = null;
}
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node node, Node parent) {
// We will continuously recurse down the AST regardless of the node types.
return true;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
Preconditions.checkNotNull(traversal);
Preconditions.checkNotNull(node);
// Searches for all the references to the arguments array.
// We don't have an arguments list set up for this scope. This implies we
// are currently in the global scope so we will not record any arguments
// array access.
if (currentArgumentsAccess == null) {
return;
}
// Otherwise, we are in a function scope and we should record if the current
// name is referring to the implicit arguments array.
if (NodeUtil.isName(node) && ARGUMENTS.equals(node.getString())) {
currentArgumentsAccess.add(node);
}
}
/**
* Tries
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> to optimize all the arguments array access in this scope by assigning
* a name to each element.
*
* @param scope scope of the function
* @return true if any modification has been done to the AST
*/
private boolean tryReplaceArguments(Scope scope) {
Node parametersList = scope.getRootNode().getFirstChild().getNext();
Preconditions.checkState(parametersList.getType() == Token.LP);
// Keep track of rather this function modified the AST and needs to be
// reported back to the compiler later.
boolean changed = false;
// Number of parameter that can be accessed without using the arguments
// array.
int numNamedParameter = parametersList.getChildCount();
// We want to guess what the highest index that has been access from the
// arguments array. We will guess that it does not use anything index higher
// than the named parameter list first until we see other wise.
int highestIndex = numNamedParameter - 1;
// Iterate through all the references to arguments array in the function to
// determine the real highestIndex.
for (Node ref : currentArgumentsAccess) {
Node getElem = ref.getParent();
// Bail on anything but argument[c] access where c is a constant.
// TODO(user): We might not need to bail out all the time, there might
// be more cases that we can cover.
if (getElem.getType() != Token.GETELEM) {
return false;
}
Node index = ref.getNext();
// We have something like arguments[x] where x is not a constant. That
// means at least one of the access is not known.
if (index.getType() != Token.NUMBER) {
// TODO(user): Its possible not to give up just yet. The type
// inference did a 'semi value propagation'. If we know that string
// is never a subclass of the type of the index. We'd know that
// it is never 'callee'.
return false; // Give up.
}
Node getElemParent = getElem.getParent();
// When we have argument[0](), replacing it with a() is semantically
// different if argument[0] is a function call that refers to 'this'
if (NodeUtil.isCall(getElemParent) &&
getElemParent.getFirstChild() == getElem) {
// TODO(user): We can consider using .call() if aliasing that
// argument allows shorter alias for other arguments.
return false;
}
// Replace the highest index if we see an access that has a higher index
// than all the one we saw before.
int value = (int) index.getDouble();
if (value > highestIndex) {
highestIndex = value;
}
}
// Number of extra arguments we need.
// For example: function() { arguments[3] } access index 3 so
// it will need 4 extra named arguments to changed into:
// function(a,b,c,d) { d }.
int numExtraArgs = highestIndex - numNamedParameter + 1
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>;
// Temporary holds the new names as string for quick access later.
String[] argNames = new String[numExtraArgs];
// Insert the formal parameter to the method's signature.
// Example: function() --> function(r0, r1, r2)
for (int i = 0; i < numExtraArgs; i++) {
String name = getNewName();
argNames[i] = name;
parametersList.addChildrenToBack(Node.newString(Token.NAME, name));
changed = true;
}
// This loop performs the replacement of arguments[x] -> a if x is known.
for (Node ref : currentArgumentsAccess) {
Node index = ref.getNext();
// Skip if it is unknown.
if (index.getType() != Token.NUMBER) {
continue;
}
int value = (int) index.getDouble();
// Unnamed parameter.
if (value >= numNamedParameter) {
ref.getParent().getParent().replaceChild(ref.getParent(),
Node.newString(Token.NAME, argNames[value - numNamedParameter]));
} else {
// Here, for no apparent reason, the user is accessing a named parameter
// with arguments[idx]. We can replace it with the actual name for them.
Node name = parametersList.getFirstChild();
// This is a linear search for the actual name from the signature.
// It is not necessary to make this fast because chances are the user
// will not deliberately write code like this.
for (int i = 0; i < value; i++) {
name = name.getNext();
}
ref.getParent().getParent().replaceChild(ref.getParent(),
Node.newString(Token.NAME, name.getString()));
}
changed = true;
}
return changed;
}
/**
* Generate a unique name for the next parameter.
*/
private String getNewName() {
return paramPredix + uniqueId++;
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Branch> entry = cfg.getEntry();
prioritizeFromEntryNode(entry);
if (shouldTraverseFunctions) {
// If we're traversing inner functions, we need to rank the
// priority of them too.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
Node value = candidate.getValue();
if (value != null && value.getType() == Token.FUNCTION) {
Preconditions.checkState(
!nodePriorities.containsKey(candidate) || candidate == entry);
prioritizeFromEntryNode(candidate);
}
}
}
// At this point, all reachable nodes have been given a priority, but
// unreachable nodes have not been given a priority. Put them last.
// Presumably, it doesn't really matter what priority they get, since
// this shouldn't happen in real code.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
if (!nodePriorities.containsKey(candidate)) {
nodePriorities.put(candidate, ++priorityCounter);
}
}
// Again, the implicit return node is always last.
nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter);
}
/**
* Given an entry node, find all the nodes reachable from that node
* and prioritize them.
*/
private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) {
PriorityQueue<DiGraphNode<Node, Branch>> worklist =
new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator);
worklist.add(entry);
while (!worklist.isEmpty()) {
DiGraphNode<Node, Branch> current = worklist.remove();
if (nodePriorities.containsKey(current)) {
continue;
}
nodePriorities.put(current, ++priorityCounter);
List<DiGraphNode<Node, Branch>> successors =
cfg.getDirectedSuccNodes(current);
for (DiGraphNode<Node, Branch> candidate : successors) {
worklist.add(candidate);
}
}
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
astPosition.put(n, astPositionCounter++);
switch (n.getType()) {
case Token.FUNCTION:
if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) {
exceptionHandler.push(n);
return true;
}
return false;
case Token.TRY:
exceptionHandler.push(n);
return true;
}
/*
* We are going to stop the traversal depending on what the node's parent
* is.
*
* We are only interested in adding edges between nodes that change control
* flow. The most obvious ones are loops and IF-ELSE's. A statement
* transfers control to its next sibling.
*
* In case of an expression tree, there is no control flow within the tree
* even when there are short circuited
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> operators and conditionals. When we
* are doing data flow analysis, we will simply synthesize lattices up the
* expression tree by finding the meet at each expression node.
*
* For example: within a Token.SWITCH, the expression in question does not
* change the control flow and need not to be considered.
*/
if (parent != null) {
switch (parent.getType()) {
case Token.FOR:
// Only traverse the body of the for loop.
return n == parent.getLastChild();
// Skip the conditions.
case Token.IF:
case Token.WHILE:
case Token.WITH:
return n != parent.getFirstChild();
case Token.DO:
return n != parent.getFirstChild().getNext();
// Only traverse the body of the cases
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.LABEL:
return n != parent.getFirstChild();
case Token.FUNCTION:
return n == parent.getFirstChild().getNext().getNext();
case Token.CONTINUE:
case Token.BREAK:
case Token.EXPR_RESULT:
case Token.VAR:
case Token.RETURN:
case Token.THROW:
return false;
case Token.TRY:
/* Just before we are about to visit the second child of the TRY node,
* we know that we will be visiting either the CATCH or the FINALLY.
* In other words, we know that the post order traversal of the TRY
* block has been finished, no more exceptions can be caught by the
* handler at this TRY block and should be taken out of the stack.
*/
if (n == parent.getFirstChild().getNext()) {
Preconditions.checkState(exceptionHandler.peek() == parent);
exceptionHandler.pop();
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.IF:
handleIf(n);
return;
case Token.WHILE:
handleWhile(n);
return;
case Token.DO:
handleDo(n);
return;
case Token.FOR:
handleFor(n);
return;
case Token.SWITCH:
handleSwitch(n);
return;
case Token.CASE:
handleCase(n);
return;
case Token.DEFAULT:
handleDefault(n);
return;
case Token.BLOCK:
case Token.SCRIPT:
handleStmtList(n);
return;
case Token.FUNCTION:
handleFunction(n);
return;
case Token.EXPR_RESULT:
handleExpr(n);
return;
case Token.THROW:
handleThrow(n);
return;
case Token.TRY:
handleTry(n);
return;
case Token.CATCH:
handleCatch(n);
return;
case Token.BREAK:
handleBreak(n);
return;
case Token.CONTINUE:
handleContinue(n);
return;
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> case Token.RETURN:
handleReturn(n);
return;
case Token.WITH:
handleWith(n);
return;
case Token.LABEL:
return;
default:
handleStmt(n);
return;
}
}
private void handleIf(Node node) {
Node thenBlock = node.getFirstChild().getNext();
Node elseBlock = thenBlock.getNext();
createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock));
if (elseBlock == null) {
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this)); // not taken branch
} else {
createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock));
}
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleWhile(Node node) {
// Control goes to the first statement if the condition evaluates to true.
createEdge(node, Branch.ON_TRUE,
computeFallThrough(node.getFirstChild().getNext()));
// Control goes to the follow() if the condition evaluates to false.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleDo(Node node) {
// The first edge can be the initial iteration as well as the iterations
// after.
createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild()));
// The edge that leaves the do loop if the condition fails.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleFor(Node forNode) {
if (forNode.getChildCount() == 4) {
// We have for (init; cond; iter) { body }
Node init = forNode.getFirstChild();
Node cond = init.getNext();
Node iter = cond.getNext();
Node body = iter.getNext();
// After initialization, we transfer to the FOR which is in charge of
// checking the condition (for the first time).
createEdge(init, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
// The end of the body will have a unconditional branch to our iter
// (handled by calling computeFollowNode of the last instruction of the
// body. Our iter will jump to the forNode again to another condition
// check.
createEdge(iter, Branch.UNCOND, forNode);
connectToPossibleExceptionHandler(init, init);
connectToPossibleExceptionHandler(forNode, cond);
connectToPossibleExceptionHandler(iter,
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> iter);
} else {
// We have for (item in collection) { body }
Node item = forNode.getFirstChild();
Node collection = item.getNext();
Node body = collection.getNext();
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
connectToPossibleExceptionHandler(forNode, collection);
}
}
private void handleSwitch(Node node) {
// Transfer to the first non-DEFAULT CASE. if there are none, transfer
// to the DEFAULT or the EMPTY node.
Node next = getNextSiblingOfType(
node.getFirstChild().getNext(), Token.CASE, Token.EMPTY);
if (next != null) { // Has at least one CASE or EMPTY
createEdge(node, Branch.UNCOND, next);
} else { // Has no CASE but possibly a DEFAULT
if (node.getFirstChild().getNext() != null) {
createEdge(node, Branch.UNCOND, node.getFirstChild().getNext());
} else { // No CASE, no DEFAULT
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleCase(Node node) {
// Case is a bit tricky....First it goes into the body if condition is true.
createEdge(node, Branch.ON_TRUE,
node.getFirstChild().getNext());
// Look for the next CASE, skipping over DEFAULT.
Node next = getNextSiblingOfType(node.getNext(), Token.CASE);
if (next != null) { // Found a CASE
Preconditions.checkState(next.getType() == Token.CASE);
createEdge(node, Branch.ON_FALSE, next);
} else { // No more CASE found, go back and search for a DEFAULT.
Node parent = node.getParent();
Node deflt = getNextSiblingOfType(
parent.getFirstChild().getNext(), Token.DEFAULT);
if (deflt != null) { // Has a DEFAULT
createEdge(node, Branch.ON_FALSE, deflt);
} else { // No DEFAULT found, go to the follow of the SWITCH.
createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmt
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>List(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.getType() == Token.BLOCK && parent != null &&
parent.getType() == Token.TRY &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since control doesn't go into that
// function (unless it is called)
while (child != null && child.getType() == Token.FUNCTION) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.getType() == Token.BLOCK && node.isSyntheticBlock()) {
createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this));
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node lastJump;
Node parent = node.getParent();
/*
* Continuously look up the ancestor tree for the BREAK target or the target
* with the corresponding label and connect to it. If along the path we
* discover a FINALLY, we will connect the BREAK to that FINALLY. From then
* on, we will just record the control flow changes in the finallyMap. This
* is due to the fact that we need to connect any node that leaves its
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> own
* FINALLY block to the outer FINALLY or the BREAK's target but those nodes
* are not known yet due to the way we traverse the nodes.
*/
for (cur = node, lastJump = node;
!isBreakTarget(cur, label);
cur = parent, parent = parent.getParent()) {
if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFallThrough(
cur.getLastChild()));
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find break target.");
}
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this));
} else {
finallyMap.put(lastJump, computeFollowNode(cur, this));
}
}
private void handleContinue(Node node) {
String label = null;
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node lastJump;
// Similar to handBreak's logic with a few minor variation.
Node parent = node.getParent();
for (cur = node, lastJump = node;
!isContinueTarget(cur, parent, label);
cur = parent, parent = parent.getParent()) {
if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, cur.getLastChild());
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find continue target.");
}
Node iter = cur;
if (cur.getChildCount() == 4) {
iter = cur.getFirstChild().getNext().getNext();
}
if (lastJump == node) {
createEdge(node, Branch.UNCOND, iter);
} else {
finallyMap.put(lastJump, iter);
}
}
private void handleReturn(Node node) {
Node lastJump = null;
for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) {
Node curHandler = iter.next();
if (NodeUtil.isFunction(curHandler)) {
break;
}
if (NodeUtil.hasFinally(curHandler)) {
if (lastJump == null) {
createEdge(node, Branch.UNCOND, curHandler.getLastChild());
} else {
finallyMap.put(lastJump,
computeFallThrough(curHandler.getLastChild()));
}
lastJump = curHandler;
}
}
if (node.hasChildren()) {
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
if (last
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Jump == null) {
createEdge(node, Branch.UNCOND, null);
} else {
finallyMap.put(lastJump, null);
}
}
private void handleStmt(Node node) {
// Simply transfer to the next line.
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) {
return computeFollowNode(node, node, cfa);
}
static Node computeFollowNode(Node node) {
return computeFollowNode(node, node, null);
}
/**
* Computes the follow() node of a given node and its parent. There is a side
* effect when calling this function. If this function computed an edge that
* exists a FINALLY, it'll attempt to connect the fromNode to the outer
* FINALLY according to the finallyMap.
*
* @param fromNode The original source node since {@code node} is changed
* during recursion.
* @param node The node that follow() should compute.
*/
private static Node computeFollowNode(
Node fromNode, Node node, ControlFlowAnalysis cfa) {
/*
* This is the case where:
*
* 1. Parent is null implies that we are transferring control to the end of
* the script.
*
* 2. Parent is a function implies that we are transferring control back to
* the caller of the function.
*
* 3. If the node is a return statement, we should also transfer control
* back to the caller of the function.
*
* 4. If the node is root then we have reached the end of what we have been
* asked to traverse.
*
* In all cases we should transfer control to a "symbolic return" node.
* This will make life easier for DFAs.
*/
Node parent = node.getParent();
if (parent == null || parent.getType() == Token.FUNCTION ||
(cfa != null && node == cfa.root)) {
return null;
}
// If we are just before a IF/WHILE/DO/FOR:
switch (parent.getType()) {
// The follow() of any of the path from IF would be what follows IF.
case Token.IF:
return computeFollowNode(fromNode, parent, cfa);
case Token.CASE:
case Token.DEFAULT:
// After the body of a CASE, the control goes to the body of the next
// case, without having to go to the case condition.
if (parent.getNext() != null) {
if (parent.getNext().getType() == Token.CASE) {
return parent.getNext().getFirstChild().getNext();
} else if (parent.getNext().getType() == Token.DEFAULT) {
return parent.getNext().getFirstChild();
} else {
Preconditions.checkState(false, "Not reachable");
}
} else {
return computeFollowNode
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(fromNode, parent, cfa);
}
break;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
return parent;
} else {
return parent.getFirstChild().getNext().getNext();
}
case Token.WHILE:
case Token.DO:
return parent;
case Token.TRY:
// If we are coming out of the TRY block...
if (parent.getFirstChild() == node) {
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(parent.getLastChild());
} else { // and have no FINALLY.
return computeFollowNode(fromNode, parent, cfa);
}
// CATCH block.
} else if (NodeUtil.getCatchBlock(parent) == node){
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(node.getNext());
} else {
return computeFollowNode(fromNode, parent, cfa);
}
// If we are coming out of the FINALLY block...
} else if (parent.getLastChild() == node){
if (cfa != null) {
for (Node finallyNode : cfa.finallyMap.get(parent)) {
cfa.createEdge(fromNode, Branch.UNCOND, finallyNode);
}
}
return computeFollowNode(fromNode, parent, cfa);
}
}
// Now that we are done with the special cases follow should be its
// immediate sibling, unless its sibling is a function
Node nextSibling = node.getNext();
// Skip function declarations because control doesn't get pass into it.
while (nextSibling != null && nextSibling.getType() == Token.FUNCTION) {
nextSibling = nextSibling.getNext();
}
if (nextSibling != null) {
return computeFallThrough(nextSibling);
} else {
// If there are no more siblings, control is transfered up the AST.
return computeFollowNode(fromNode, parent, cfa);
}
}
/**
* Computes the destination node of n when we want to fallthough into the
* subtree of n. We don't always create a CFG edge into n itself because of
* DOs and FORs.
*/
static Node computeFallThrough(Node n) {
switch (n.getType()) {
case Token.DO:
return computeFallThrough(n.getFirstChild());
case Token.FOR:
if (NodeUtil.isForIn(n)) {
return n;
}
return computeFallThrough(n.getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (NodeUtil.isFunction(handler)) {
return;
}
Preconditions.checkState(handler.getType() == Token.TRY);
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types) {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(), label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.getType() == Token.LABEL) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* Get the TRY block with a CATCH that would be run if n throws an exception.
* @return The CATCH node or null if it there isn't a CATCH before the
* the function terminates.
*/
static Node getExceptionHandler(Node n) {
for (Node cur = n;
cur.getType() != Token.SCRIPT && cur.getType() != Token.FUNCTION;
cur = cur.getParent()) {
Node catchNode = getCatchHandlerForBlock(cur);
if (catchNode != null) {
return catchNode;
}
}
return null;
}
/**
* Locate the catch BLOCK given the first block in a TRY.
* @return The CATCH node or null there is no catch handler.
*/
static Node getCatchHandlerForBlock(Node block) {
if (block.getType() == Token.BLOCK &&
block.getParent().getType() == Token.TRY &&
block.getParent().getFirstChild() == block) {
for (Node s = block.getNext(); s != null; s = s.getNext()) {
if (NodeUtil.hasCatchHandler(s)) {
return s.getFirstChild();
}
}
}
return null;
}
/**
* A {@link ControlFlowGraph} which provides a node comparator based on the
* pre-order traversal of the AST.
*/
private static class AstControlFlowGraph extends ControlFlowGraph<Node> {
private final Map<DiGraphNode<Node, Branch>, Integer> priorities;
/**
* Constructor.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> * @param entry The entry node.
* @param priorities The map from nodes to position in the AST (to be
* filled by the {@link ControlFlowAnalysis#shouldTraverse}).
*/
private AstControlFlowGraph(Node entry,
Map<DiGraphNode<Node, Branch>, Integer> priorities,
boolean edgeAnnotations) {
super(entry,
true /* node annotations */, edgeAnnotations);
this.priorities = priorities;
}
@Override
/**
* Returns a node comparator based on the pre-order traversal of the AST.
* @param isForward x 'before' y in the pre-order traversal implies
* x 'less than' y (if true) and x 'greater than' y (if false).
*/
public Comparator<DiGraphNode<Node, Branch>> getOptionalNodeComparator(
boolean isForward) {
if (isForward) {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n1) - getPosition(n2);
}
};
} else {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n2) - getPosition(n1);
}
};
}
}
/**
* Gets the pre-order traversal position of the given node.
* @return An arbitrary counter used for comparing positions.
*/
private int getPosition(DiGraphNode<Node, Branch> n) {
Integer priority = priorities.get(n);
Preconditions.checkNotNull(priority);
return priority;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> Unlike most types in our type system, the collection of types returned
* will not be collapsed. This means that if a type is defined on
* {@code Object} and on {@code Array}, this method must return
* {@code [Object, Array]}. It would not be correct to collapse them to
* {@code [Object]}.
*/
public Iterable<ObjectType> getEachReferenceTypeWithProperty(
String propertyName) {
if (eachRefTypeIndexedByProperty.containsKey(propertyName)) {
return eachRefTypeIndexedByProperty.get(propertyName).values();
} else {
return ImmutableList.of();
}
}
/**
* Increments the current generation. Clients must call this in order to
* move to the next generation of type resolution, allowing types to attempt
* resolution again.
*/
public void incrementGeneration() {
for (NamedType type : resolvedNamedTypes.values()) {
type.clearResolved();
}
unresolvedNamedTypes.putAll(resolvedNamedTypes);
resolvedNamedTypes.clear();
}
boolean isLastGeneration() {
return lastGeneration;
}
/**
* Sets whether this is the last generation. In the last generation,
* {@link NamedType} warns about unresolved types.
*/
public void setLastGeneration(boolean lastGeneration) {
this.lastGeneration = lastGeneration;
}
/**
* Tells the type system that {@code type} implements interface {@code
* InterfaceInstance}.
* {@code inter} must be an ObjectType for the instance of the interface as it
* could be a named type and not yet have the constructor.
*/
void registerTypeImplementingInterface(
FunctionType type, ObjectType interfaceInstance) {
interfaceToImplementors.put(interfaceInstance.getReferenceName(), type);
}
/**
* Returns a collection of types that directly implement {@code
* interfaceInstance}. Subtypes of implementing types are not guaranteed to
* be returned. {@code interfaceInstance} must be an ObjectType for the
* instance of the interface.
*/
public Collection<FunctionType> getDirectImplementors(
ObjectType interfaceInstance) {
return interfaceToImplementors.get(interfaceInstance.getReferenceName());
}
/**
* Records declared global type names. This makes resolution faster
* and more robust in the common case.
*
* @param name The name of the type to be recorded.
* @param t The actual type being associated with the name.
* @return True if this name is not already defined, false otherwise.
*/
public boolean declareType(String name, JSType t) {
if (namesToTypes.containsKey(name)) {
return false;
}
register(t, name);
return true;
}
/**
* Overrides a declared global type name. Throws an exception if this
* type name hasn't been declared yet.
*/
public void overwriteDeclaredType(String name, JSType t) {
Preconditions.checkState(namesToTypes.containsKey(name));
register(t, name);
}
/**
* Records a forward
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>-declared type name. We will not emit errors if this
* type name never resolves to anything.
*/
public void forwardDeclareType(String name) {
forwardDeclaredTypes.add(name);
}
/**
* Whether this is a forward-declared type name.
*/
public boolean isForwardDeclaredType(String name) {
return forwardDeclaredTypes.contains(name);
}
/** Determines whether the given JS package exists. */
public boolean hasNamespace(String name) {
return namespaces.contains(name);
}
/**
* Looks up a type by name.
*
* @param jsTypeName The name string.
* @return the corresponding JSType object or {@code null} it cannot be found
*/
public JSType getType(String jsTypeName) {
// TODO(user): Push every local type name out of namesToTypes so that
// NamedType#resolve is correct.
if (jsTypeName.equals(templateTypeName)) {
return templateType;
}
return namesToTypes.get(jsTypeName);
}
public JSType getNativeType(JSTypeNative typeId) {
return nativeTypes[typeId.ordinal()];
}
public ObjectType getNativeObjectType(JSTypeNative typeId) {
return (ObjectType) getNativeType(typeId);
}
public FunctionType getNativeFunctionType(JSTypeNative typeId) {
return (FunctionType) getNativeType(typeId);
}
/**
* Try to resolve a type name, but forgive the user and don't emit
* a warning if this doesn't resolve.
*/
public JSType getForgivingType(StaticScope<JSType> scope, String jsTypeName,
String sourceName, int lineno, int charno) {
JSType type = getType(
scope, jsTypeName, sourceName, lineno, charno);
type.forgiveUnknownNames();
return type;
}
/**
* Looks up a type by name. To allow for forward references to types, an
* unrecognized string has to be bound to a NamedType object that will be
* resolved later.
*
* @param scope A scope for doing type name resolution.
* @param jsTypeName The name string.
* @param sourceName The name of the source file where this reference appears.
* @param lineno The line number of the reference.
* @return a NamedType if the string argument is not one of the known types,
* otherwise the corresponding JSType object.
*/
public JSType getType(StaticScope<JSType> scope, String jsTypeName,
String sourceName, int lineno, int charno) {
JSType type = getType(jsTypeName);
if (type == null) {
// TODO(user): Each instance should support named type creation using
// interning.
NamedType namedType =
new NamedType(this, jsTypeName, sourceName, lineno, charno);
unresolvedNamedTypes.put(scope, namedType);
type = namedType;
}
return type;
}
/**
* Resolve all the un
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>resolved types in the given scope.
*/
public void resolveTypesInScope(StaticScope<JSType> scope) {
for (NamedType type : unresolvedNamedTypes.get(scope)) {
type.resolve(reporter, scope);
}
resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope));
if (scope != null && scope.getParentScope() == null) {
// By default, the global "this" type is just an anonymous object.
// If the user has defined a Window type, make the Window the
// implicit prototype of "this".
PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType(
JSTypeNative.GLOBAL_THIS);
JSType windowType = getType("Window");
if (globalThis.isUnknownType()) {
ObjectType windowObjType = ObjectType.cast(windowType);
if (windowObjType != null) {
globalThis.setImplicitPrototype(windowObjType);
} else {
globalThis.setImplicitPrototype(
getNativeObjectType(JSTypeNative.OBJECT_TYPE));
}
}
}
}
/**
* Creates a type representing optional values of the given type.
* @return the union of the type and the void type
*/
public JSType createOptionalType(JSType type) {
if (type instanceof UnknownType || type.isAllType()) {
return type;
} else {
return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE));
}
}
/**
* Creates a type representing nullable values of the given type.
* @return the union of the type and the Null type
*/
public JSType createDefaultObjectUnion(JSType type) {
return shouldTolerateUndefinedValues()
? createOptionalNullableType(type)
: createNullableType(type);
}
/**
* Creates a type representing nullable values of the given type.
* @return the union of the type and the Null type
*/
public JSType createNullableType(JSType type) {
return createUnionType(type, getNativeType(JSTypeNative.NULL_TYPE));
}
/**
* Creates a nullabel and undefine-able value of the given type.
* @return The union of the type and null and undefined.
*/
public JSType createOptionalNullableType(JSType type) {
return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE),
getNativeType(JSTypeNative.NULL_TYPE));
}
/**
* Creates a union type whose variants are the arguments.
*/
public JSType createUnionType(JSType... variants) {
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (JSType type : variants) {
builder.addAlternate(type);
}
return builder.build();
}
/**
* Creates a union type whose variants are the builtin types specified
* by the arguments.
*/
public JSType createUnionType(JSTypeNative... variants) {
UnionTypeBuilder builder = new UnionTypeBuilder
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>, objectType, parameterType);
}
/**
* Creates a named type.
*/
@VisibleForTesting
public JSType createNamedType(String reference,
String sourceName, int lineno, int charno) {
return new NamedType(this, reference, sourceName, lineno, charno);
}
/**
* Identifies the name of a typedef or enum before we actually declare it.
*/
public void identifyNonNullableName(String name) {
Preconditions.checkNotNull(name);
nonNullableTypeNames.add(name);
}
/**
* Creates a JSType from the nodes representing a type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
public JSType createFromTypeNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
return createFromTypeNodes(n, sourceName, scope, false);
}
/**
* Creates a JSType from the nodes representing a type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
* @param forgiving Whether we should be forgiving about type names
* that we can't find.
*/
public JSType createFromTypeNodes(Node n, String sourceName,
StaticScope<JSType> scope, boolean forgiving) {
if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) {
// If the type expression doesn't contain any names, just
// resolve it anyway.
boolean hasNames = hasTypeName(n);
if (hasNames) {
return new UnresolvedTypeExpression(this, n, sourceName, forgiving);
}
}
return createFromTypeNodesInternal(n, sourceName, scope, forgiving);
}
private boolean hasTypeName(Node n) {
if (n.getType() == Token.STRING) {
return true;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (hasTypeName(child)) {
return true;
}
}
return false;
}
/** @see #createFromTypeNodes(Node, String, StaticScope, boolean) */
private JSType createFromTypeNodesInternal(Node n, String sourceName,
StaticScope<JSType> scope, boolean forgiving) {
switch (n.getType()) {
case Token.LC: // Record type.
return createRecordTypeFromNodes(
n.getFirstChild(), sourceName, scope);
case Token.BANG: // Not nullable
return createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope, forgiving)
.restrictByNotNullOrUndefined();
case Token.QMARK: // Nullable or unknown
Node firstChild = n.getFirstChild();
if (firstChild == null) {
return getNativeType(UNKNOWN_TYPE);
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> return createDefaultObjectUnion(
createFromTypeNodesInternal(
firstChild, sourceName, scope, forgiving));
case Token.EQUALS: // Optional
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope, false));
case Token.ELLIPSIS: // Var args
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope, false));
case Token.STAR: // The AllType
return getNativeType(ALL_TYPE);
case Token.LB: // Array type
// TODO(nicksantos): Enforce membership restrictions on the Array.
return getNativeType(ARRAY_TYPE);
case Token.PIPE: // Union type
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
builder.addAlternate(
createFromTypeNodesInternal(child, sourceName, scope, false));
}
return builder.build();
case Token.EMPTY: // When the return value of a function is not specified
return getNativeType(UNKNOWN_TYPE);
case Token.VOID: // Only allowed in the return value of a function.
return getNativeType(VOID_TYPE);
case Token.STRING:
JSType namedType = getType(scope, n.getString(), sourceName,
n.getLineno(), n.getCharno());
if (forgiving) {
namedType.forgiveUnknownNames();
}
if (resolveMode != ResolveMode.LAZY_NAMES) {
namedType = namedType.resolveInternal(reporter, scope);
}
if ((namedType instanceof ObjectType) &&
!(nonNullableTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
if (typeList != null &&
("Array".equals(n.getString()) ||
"Object".equals(n.getString()))) {
JSType parameterType =
createFromTypeNodesInternal(
typeList.getLastChild(), sourceName, scope, false);
namedType = new ParameterizedType(
this, (ObjectType) namedType, parameterType);
if (typeList.hasMoreThanOneChild()) {
JSType indexType =
createFromTypeNodesInternal(
typeList.getFirstChild(), sourceName, scope, false);
namedType = new IndexedType(
this, (ObjectType) namedType, indexType);
}
}
return createDefaultObjectUnion(namedType);
} else {
return namedType;
}
case Token.FUNCTION:
ObjectType thisType = null;
Node current = n.getFirstChild();
if (current.getType() == Token.THIS) {
Node thisNode = current.getFirstChild();
thisType =
ObjectType.cast(
createFromTypeNodesInternal(
thisNode, sourceName, scope, false)
.restrictByNotNullOrUndefined());
if (thisType == null) {
reporter.warning(
ScriptRuntime.getMessage0("msg.js
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>doc.function.thisnotobject"),
sourceName, thisNode.getLineno(), "", thisNode.getCharno());
}
current = current.getNext();
}
FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this);
if (current.getType() == Token.LP) {
Node args = current.getFirstChild();
for (Node arg = current.getFirstChild(); arg != null;
arg = arg.getNext()) {
if (arg.getType() == Token.ELLIPSIS) {
if (arg.getChildCount() == 0) {
paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE));
} else {
paramBuilder.addVarArgs(
createFromTypeNodesInternal(
arg.getFirstChild(), sourceName, scope, false));
}
} else {
JSType type = createFromTypeNodesInternal(
arg, sourceName, scope, false);
if (arg.getType() == Token.EQUALS) {
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess) {
reporter.warning(
ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"),
sourceName, arg.getLineno(), "", arg.getCharno());
}
} else {
paramBuilder.addRequiredParams(type);
}
}
}
current = current.getNext();
}
JSType returnType =
createFromTypeNodesInternal(current, sourceName, scope, false);
return new FunctionBuilder(this)
.withParams(paramBuilder)
.withReturnType(returnType)
.withTypeOfThis(thisType)
.build();
}
throw new IllegalStateException(
"Unexpected node in type expression: " + n.toString());
}
/**
* Creates a RecordType from the nodes representing said record type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
private JSType createRecordTypeFromNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
RecordTypeBuilder builder = new RecordTypeBuilder(this);
// For each of the fields in the record type.
for (Node fieldTypeNode = n.getFirstChild();
fieldTypeNode != null;
fieldTypeNode = fieldTypeNode.getNext()) {
// Get the property's name.
Node fieldNameNode = fieldTypeNode;
boolean hasType = false;
if (fieldTypeNode.getType() == Token.COLON) {
fieldNameNode = fieldTypeNode.getFirstChild();
hasType = true;
}
String fieldName = fieldNameNode.getString();
// TODO(user): Move this into the lexer/parser.
// Remove the string literal characters around a field name,
// if any.
if (fieldName.startsWith("'") || fieldName.startsWith("\"")) {
fieldName = fieldName.substring(1, fieldName.length() - 1);
}
// Get the property's type.
JSType fieldType = null;
if (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
* Gets a comparator for the nodes. The default implementation returns
* {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}.
* @param isForward Whether the comparator sorts the nodes in the direction of
* the flow.
* @return a comparator or null (in particular, if not overriden)
*/
public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/** Exception related. */
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that is also represented by another control flow graph
* node.
*
* <p>For example, traversing an IF node as root will visit the two subtree
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edge.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
case Token.FINALLY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// is bleed into the local scope and parameters has been assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body represent by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// Theses control structure is represented by its node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
// The FOR(;;) node differs from other control structure in that
// it has a initialization and a increment statement. Those
//
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> two statements have its corresponding CFG nodes to represent them.
// The FOR node represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++; }
if (NodeUtil.isForIn(parent)) {
return n == parent.getLastChild();
} else {
return NodeUtil.getConditionExpression(parent) != n;
}
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.WITH:
return n != parent.getFirstChild();
default:
return false;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Norris Boyd
* Roger Lawrence
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino;
public class FunctionNode extends ScriptOrFnNode {
private static final long serialVersionUID = 1L;
public FunctionNode(String name) {
super(Token.FUNCTION);
functionName = name;
}
public FunctionNode(String name, int lineno, int charno) {
super(Token.FUNCTION, lineno, charno);
functionName = name;
}
public String getFunctionName() {
return functionName;
}
public boolean requiresActivation() {
return itsNeedsActivation;
}
public boolean getIgnoreDynamicScope() {
return itsIgnoreDynamicScope;
}
/**
* There are three types of functions that can be defined. The first
* is a function statement. This is a function appearing as a top-level
* statement (i.e., not nested inside some other statement) in either a
* script or a function.
*
* The second is a function expression, which is a function appearing in
* an expression except for the third type, which is...
*
* The third type is a function expression where the expression is the
* top-level expression in an expression statement.
*
* The three
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> void setEnumParameterType(JSTypeExpression type) {
setType(type, TYPEFIELD_ENUM);
}
void setTypedefType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPEDEF);
}
private void setType(JSTypeExpression type, int mask) {
if ((bitset & MASK_TYPEFIELD) != 0) {
throw new IllegalStateException(
"API tried to add two incompatible type tags. " +
"This should have been blocked and emitted a warning.");
}
this.bitset = (bitset & MASK_FLAGS) | mask;
this.type = type;
}
/**
* Returns the list of thrown types.
*/
public List<JSTypeExpression> getThrownTypes() {
if (info == null || info.thrownTypes == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.thrownTypes);
}
/**
* Returns whether a type, specified using the {@code @type} annotation, is
* present on this JSDoc.
*/
public boolean hasType() {
return hasType(TYPEFIELD_TYPE);
}
/**
* Returns whether an enum parameter type, specified using the {@code @enum}
* annotation, is present on this JSDoc.
*/
public boolean hasEnumParameterType() {
return hasType(TYPEFIELD_ENUM);
}
/**
* Returns whether a typedef parameter type, specified using the
* {@code @typedef} annotation, is present on this JSDoc.
*/
public boolean hasTypedefType() {
return hasType(TYPEFIELD_TYPEDEF);
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @return}
* annotation.
*/
public boolean hasReturnType() {
return hasType(TYPEFIELD_RETURN);
}
private boolean hasType(int mask) {
return (bitset & MASK_TYPEFIELD) == mask;
}
/**
* Gets the type specified by the {@code @type} annotation.
*/
public JSTypeExpression getType() {
return getType(TYPEFIELD_TYPE);
}
/**
* Gets the return type specified by the {@code @return} annotation.
*/
public JSTypeExpression getReturnType() {
return getType(TYPEFIELD_RETURN);
}
/**
* Gets the enum parameter type specified by the {@code @enum} annotation.
*/
public JSTypeExpression getEnumParameterType() {
return getType(TYPEFIELD_ENUM);
}
/**
* Gets the typedef type specified by the {@code @type} annotation.
*/
public JSTypeExpression getTypedefType() {
return getType(TYPEFIELD_TYPEDEF);
}
private JSTypeExpression getType(int typefield) {
if ((MASK_TYPEFIELD & bitset) == typefield) {
return type;
} else {
return null;
}
}
/**
* Gets the type specified by the {@code @this} annotation.
*/
public JSTypeExpression getThisType() {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> parent;
/**
* Full chain of ASSIGN ancestors
*/
final List<Node> assignAncestors = Lists.newArrayList();
/**
* The last ancestor
*/
final Node lastAncestor;
/**
* Data structure for information about a removable assignment.
*
* @param nameNode The LHS
* @param assignNode The parent ASSIGN node
* @param traversal Access to further levels, assumed to start at 1
*/
public RemovableAssignment(Node nameNode, Node assignNode,
NodeTraversal traversal) {
this.node = nameNode;
this.parent = assignNode;
Node ancestor = assignNode;
do {
ancestor = ancestor.getParent();
assignAncestors.add(ancestor);
} while (ancestor.getType() == Token.ASSIGN &&
ancestor.getFirstChild().isQualifiedName());
lastAncestor = ancestor.getParent();
}
/**
* Remove this node.
*/
public void remove() {
Node rhs = node.getNext();
Node last = parent;
for (Node ancestor : assignAncestors) {
if (NodeUtil.isExpressionNode(ancestor)) {
lastAncestor.removeChild(ancestor);
} else {
rhs.detachFromParent();
ancestor.replaceChild(last, rhs);
}
last = ancestor;
}
compiler.reportCodeChange();
}
}
/**
* Identifies all assignments of the abstract method to a variable.
*/
private class FindAbstractMethods extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.ASSIGN) {
Node nameNode = n.getFirstChild();
Node valueNode = n.getLastChild();
if (nameNode.isQualifiedName() &&
valueNode.isQualifiedName() &&
ABSTRACT_METHOD_NAME.equals(valueNode.getQualifiedName())) {
abstractMethodAssignmentNodes.add(new RemovableAssignment(
n.getFirstChild(), n, t));
}
}
}
}
/**
* Identifies all assertion calls.
*/
private class FindAssertionCalls extends AbstractPostOrderCallback {
Set<String> assertionNames = Sets.newHashSet();
FindAssertionCalls() {
for (AssertionFunctionSpec spec :
compiler.getCodingConvention().getAssertionFunctions()) {
assertionNames.add(spec.getFunctionName());
}
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.CALL) {
String fnName = n.getFirstChild().getQualifiedName();
if (assertionNames.contains(fnName)) {
assertionCalls.add(n);
}
}
}
}
/**
* Creates a Closure code remover.
*
* @param compiler The AbstractCompiler
* @param removeAbstractMethods Remove declarations of abstract methods.
* @param removeAssertionCalls Remove calls to goog.assert functions.
*/
ClosureCodeRemoval(AbstractCompiler compiler, boolean removeAbstractMethods,
boolean removeAssertionCalls) {
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.
*/
static final String DELEGATE_PROXY_SUFFIX =
ObjectType.createDelegateSuffix("Proxy");
private static final String LEGACY_TYPEDEF = "goog.typedef";
static final DiagnosticType MALFORMED_TYPEDEF =
DiagnosticType.warning(
"JSC_MALFORMED_TYPEDEF",
"Typedef for {0} does not have any type information");
static final DiagnosticType ENUM_INITIALIZER =
DiagnosticType.warning(
"JSC_ENUM_INITIALIZER_NOT_ENUM",
"enum initializer must be an object literal or an enum");
static final DiagnosticType CTOR_INITIALIZER =
DiagnosticType.warning(
"JSC_CTOR_INITIALIZER_NOT_CTOR",
"Constructor {0} must be initialized at declaration");
static final DiagnosticType IFACE_INITIALIZER =
DiagnosticType.warning(
"JSC_IFACE_INITIALIZER_NOT_IFACE",
"Interface {0} must be initialized at declaration");
static final DiagnosticType CONSTRUCTOR_EXPECTED =
DiagnosticType.warning(
"JSC_REFLECT_CONSTRUCTOR_EXPECTED",
"Constructor expected as first argument");
static final DiagnosticType UNKNOWN_LENDS =
DiagnosticType.warning(
"JSC_UNKNOWN_LENDS",
"Variable {0} not declared before @lends annotation.");
static final DiagnosticType LENDS_ON_NON_OBJECT =
DiagnosticType.warning(
"JSC_LENDS_ON_NON_OBJECT",
"May only lend properties to object types. {0} has type {1}.");
private final AbstractCompiler compiler;
private final ErrorReporter typeParsingErrorReporter;
private final TypeValidator validator;
private final CodingConvention codingConvention;
private final JSTypeRegistry typeRegistry;
private List<ObjectType> delegateProxyPrototypes = Lists.newArrayList();
/**
* Defer attachment of types to nodes until all type names
* have been resolved. Then, we can resolve the type and attach it.
*/
private class DeferredSetType {
final Node node;
final JSType type;
DeferredSetType(Node node, JSType type) {
Preconditions.checkNotNull(node);
Preconditions.checkNotNull(type);
this.node = node;
this.type = type;
// Other parts of this pass may read off the node.
// (like when we set the LHS of an assign with a typed RHS function.)
node.setJSType(type);
}
void resolve(Scope scope) {
node.setJSType(type.resolve(typeParsingErrorReporter, scope));
}
}
TypedScopeCreator(AbstractCompiler compiler) {
this(compiler, compiler.getCodingConvention());
}
TypedScopeCreator(AbstractCompiler compiler,
CodingConvention codingConvention) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.codingConvention = codingConvention;
this.typeRegistry = compiler.getTypeRegistry();
this.typeParsingErrorReporter = typeRegistry.getErrorReporter();
}
/**
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>TYPE);
declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE);
declareNativeValueType(s, "undefined", VOID_TYPE);
// The typedef construct needs the any type, so that it can be assigned
// to anything. This is kind of a hack, and an artifact of the typedef
// syntax we've chosen.
declareNativeValueType(s, LEGACY_TYPEDEF, NO_TYPE);
// ActiveXObject is unqiuely special, because it can be used to construct
// any type (the type that it creates is related to the arguments you
// pass to it).
declareNativeValueType(s, "ActiveXObject", NO_OBJECT_TYPE);
return s;
}
private void declareNativeFunctionType(Scope scope, JSTypeNative tId) {
FunctionType t = typeRegistry.getNativeFunctionType(tId);
declareNativeType(scope, t.getInstanceType().getReferenceName(), t);
declareNativeType(
scope, t.getPrototype().getReferenceName(), t.getPrototype());
}
private void declareNativeValueType(Scope scope, String name,
JSTypeNative tId) {
declareNativeType(scope, name, typeRegistry.getNativeType(tId));
}
private void declareNativeType(Scope scope, String name, JSType t) {
scope.declare(name, null, t, null, false);
}
private static class DiscoverEnumsAndTypedefs
extends AbstractShallowStatementCallback {
private final JSTypeRegistry registry;
DiscoverEnumsAndTypedefs(JSTypeRegistry registry) {
this.registry = registry;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
Node nameNode = null;
switch (node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, child.getFirstChild(),
NodeUtil.getInfoForNameNode(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.getType() == Token.ASSIGN) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getLastChild(),
firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, null, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, Node valueNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
if (valueNode != null &&
LEGACY_TYPEDEF.equals(valueNode.getQualifiedName
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>())) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
/**
* Given a node, determines whether that node names a prototype
* property, and if so, returns the qualified name node representing
* the owner of that property. Otherwise, returns null.
*/
private static Node getPrototypePropertyOwner(Node n) {
if (n.getType() == Token.GETPROP) {
Node firstChild = n.getFirstChild();
if (firstChild.getType() == Token.GETPROP &&
firstChild.getLastChild().getString().equals("prototype")) {
Node maybeOwner = firstChild.getFirstChild();
if (maybeOwner.isQualifiedName()) {
return maybeOwner;
}
}
}
return null;
}
private JSType getNativeType(JSTypeNative nativeType) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're builidng.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (n.getType() == Token.FUNCTION ||
n.getType() == Token.SCRIPT) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild() || parent == scope.getRootNode();
}
@Override
public
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> void visit(NodeTraversal t, Node n, Node parent) {
attachLiteralTypes(n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n, parent);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// VARs and ASSIGNs are handled in different branches of this
// switch statement.
if (parent.getType() != Token.ASSIGN &&
parent.getType() != Token.NAME) {
defineDeclaredFunction(n, parent);
}
break;
case Token.ASSIGN:
// Handle constructor and enum definitions.
defineNamedTypeAssign(n, parent);
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.getType() == Token.GETPROP &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n, parent);
break;
case Token.VAR:
defineVar(n, parent);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.getType() == Token.EXPR_RESULT &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
}
private void attachLiteralTypes(Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.REF_SPECIAL:
n.setJSType(getNativeType(UNKNOWN_TYPE));
break;
case Token.OBJECTLIT:
processObjectLit(n);
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void processObjectLit(Node objectLit) {
JSDocInfo info = objectLit.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
String lendsName = info.getLendsName();
Var lendsVar = scope.getVar(lendsName);
if (lendsVar == null)
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {
compiler.report(
JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName));
} else {
JSType type = lendsVar.getType();
if (type == null) {
type = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) {
compiler.report(
JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT,
lendsName, type.toString()));
} else {
objectLit.setJSType(type);
}
}
}
if (objectLit.getJSType() == null) {
objectLit.setJSType(typeRegistry.createAnonymousObjectType());
}
}
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
JSType getDeclaredTypeInAnnotation(
NodeTraversal t, Node node, JSDocInfo info) {
return getDeclaredTypeInAnnotation(t.getSourceName(), node, info);
}
JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode = node.getType() == Token.GETPROP ?
node.getFirstChild() : null;
if (info != null) {
if (info.hasType()) {
jsType = info.getType().evaluate(scope, typeRegistry);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
// constructors are often handled separately.
if (info.isConstructor() && typeRegistry.getType(fnName) != null) {
return null;
}
FunctionTypeBuilder builder =
new FunctionTypeBuilder(
fnName, compiler, node, sourceName, scope)
.inferTemplateTypeName(info)
.inferReturnType(info)
.inferParameterTypes(info)
.inferInheritance(info);
// Infer the context type.
boolean searchedForThisType = false;
if (objNode != null) {
if (objNode.getType() == Token.GETPROP &&
objNode.getLastChild().getString().equals("prototype")) {
builder.inferThisType(info, objNode.getFirstChild());
searchedForThisType = true;
} else if (objNode.getType() == Token.THIS) {
builder.inferThisType(info, objNode.getJSType());
searchedForThisType = true;
}
}
if (!searchedForThisType) {
builder.inferThisType(info, (Node) null);
}
jsType = builder.buildAndRegister();
}
}
return jsType;
}
/**
* Asserts that it's ok to define this node's name.
* The node
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> should have a source name and be of the specified type.
*/
void assertDefinitionNode(Node n, int type) {
Preconditions.checkState(sourceName != null);
Preconditions.checkState(n.getType() == type);
}
/**
* Defines a catch parameter.
*/
void defineCatch(Node n, Node parent) {
assertDefinitionNode(n, Token.CATCH);
Node catchName = n.getFirstChild();
defineSlot(catchName, n, null);
}
/**
* Defines a VAR initialization.
*/
void defineVar(Node n, Node parent) {
assertDefinitionNode(n, Token.VAR);
JSDocInfo info = n.getJSDocInfo();
if (n.hasMoreThanOneChild()) {
if (info != null) {
// multiple children
compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF));
}
for (Node name : n.children()) {
defineName(name, n, parent, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, parent,
(info != null) ? info : name.getJSDocInfo());
}
}
/**
* Defines a declared function.
*/
void defineDeclaredFunction(Node n, Node parent) {
assertDefinitionNode(n, Token.FUNCTION);
JSDocInfo info = n.getJSDocInfo();
int parentType = parent.getType();
Preconditions.checkState(
(scope.isLocal() || parentType != Token.ASSIGN) &&
parentType != Token.NAME,
"function defined as standalone function when it is being " +
"assigned");
String functionName = n.getFirstChild().getString();
FunctionType functionType = getFunctionType(functionName, n, info,
null);
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
}
/**
* Defines a qualified name assign to an enum or constructor.
*/
void defineNamedTypeAssign(Node n, Node parent) {
assertDefinitionNode(n, Token.ASSIGN);
JSDocInfo info = n.getJSDocInfo();
// TODO(nicksantos): We should support direct assignment to a
// prototype, as in:
// Foo.prototype = {
// a: function() { ... },
// b: function() { ... }
// };
// Right now (6/23/08), we understand most of this syntax, but we
// don't tie the "a" and "b" methods to the context of Foo.
Node rvalue = n.getLastChild();
Node lvalue = n.getFirstChild();
info = (info != null) ? info : rvalue.getJSDocInfo();
if (rvalue.getType() == Token.FUNCTION ||
info != null && info.isConstructor()) {
getFunctionType(l
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>value.getQualifiedName(), rvalue, info,
lvalue);
} else if (info != null && info.hasEnumParameterType()) {
JSType type = getEnumType(lvalue.getQualifiedName(), n, rvalue,
info.getEnumParameterType().evaluate(scope, typeRegistry));
if (type != null) {
setDeferredType(lvalue, type);
}
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param name The {@link Token#NAME} node.
* @param var The parent of the {@code name} node, which must be a
* {@link Token#VAR} node.
* @param parent {@code var}'s parent.
* @param info the {@link JSDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, Node parent, JSDocInfo info) {
Node value = name.getFirstChild();
if (value != null && value.getType() == Token.FUNCTION) {
// function
String functionName = name.getString();
FunctionType functionType =
getFunctionType(functionName, value, info, null);
if (functionType.isReturnTypeInferred() &&
scope.isLocal()) {
defineSlot(name, var, null);
} else {
defineSlot(name, var, functionType);
}
} else {
// variable's type
JSType type = null;
if (info == null) {
// the variable's type will be inferred
CompilerInput input = compiler.getInput(sourceName);
Preconditions.checkNotNull(input, sourceName);
type = input.isExtern() ?
getNativeType(UNKNOWN_TYPE) : null;
} else if (info.hasEnumParameterType()) {
type = getEnumType(name.getString(), var, value,
info.getEnumParameterType().evaluate(scope, typeRegistry));
} else if (info.isConstructor()) {
type = getFunctionType(name.getString(), value, info, name);
} else {
type = getDeclaredTypeInAnnotation(sourceName, name, info);
}
defineSlot(name, var, type);
}
}
/**
* Gets the function type from the function node and its attached
* {@link JSDocInfo}.
* @param name the function's name
* @param rValue the function node. It must be a {@link Token#FUNCTION}.
* @param info the {@link JSDocInfo} attached to the function definition
* @param lvalueNode The node where this function is being
* assigned. For example, {@code A.prototype.foo = ...} would be used to
* determine that this function is a method of A.prototype. May be
* null to indicate that this is not being assigned to a qualified name.
*/
private FunctionType getFunctionType(String name,
Node rValue, JSDocInfo info, @Nullable Node lvalueNode)
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> {
FunctionType functionType = null;
// Global function aliases should be registered with the type registry.
if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) {
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof FunctionType) {
functionType = (FunctionType) var.getType();
if (functionType != null &&
(functionType.isConstructor() || functionType.isInterface())) {
typeRegistry.declareType(name, functionType.getInstanceType());
}
}
return functionType;
}
Node owner = null;
if (lvalueNode != null) {
owner = getPrototypePropertyOwner(lvalueNode);
}
Node errorRoot = rValue == null ? lvalueNode : rValue;
boolean isFnLiteral =
rValue != null && rValue.getType() == Token.FUNCTION;
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ?
rValue.getFirstChild().getNext() : null;
Node fnBlock = isFnLiteral ? parametersNode.getNext() : null;
if (functionType == null && info != null && info.hasType()) {
JSType type = info.getType().evaluate(scope, typeRegistry);
// Known to be not null since we have the FUNCTION token there.
type = type.restrictByNotNullOrUndefined();
if (type.isFunctionType()) {
functionType = (FunctionType) type;
functionType.setJSDocInfo(info);
}
}
if (functionType == null) {
// Find the type of any overridden function.
FunctionType overriddenPropType = null;
if (lvalueNode != null && lvalueNode.getType() == Token.GETPROP &&
lvalueNode.isQualifiedName()) {
Var var = scope.getVar(
lvalueNode.getFirstChild().getQualifiedName());
if (var != null) {
ObjectType ownerType = ObjectType.cast(var.getType());
if (ownerType != null) {
String propName = lvalueNode.getLastChild().getString();
overriddenPropType = findOverriddenFunction(ownerType, propName);
}
}
}
functionType =
new FunctionTypeBuilder(name, compiler, errorRoot, sourceName,
scope)
.setSourceNode(fnRoot)
.inferFromOverriddenFunction(overriddenPropType, parametersNode)
.inferTemplateTypeName(info)
.inferReturnType(info)
.inferInheritance(info)
.inferThisType(info, owner)
.inferParameterTypes(parametersNode, info)
.inferReturnStatementsAsLastResort(fnBlock)
.buildAndRegister();
}
// assigning the function type to the function node
if (rValue != null) {
setDeferredType(rValue, functionType);
}
// all done
return functionType;
}
/**
* Find the function that's being overridden on this type, if any.
*/
private
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> FunctionType findOverriddenFunction(
ObjectType ownerType, String propName) {
// First, check to see if the property is implemented
// on a superclass.
JSType propType = ownerType.getPropertyType(propName);
if (propType instanceof FunctionType) {
return (FunctionType) propType;
} else {
// If it's not, then check to see if it's implemented
// on an implemented interface.
for (ObjectType iface :
ownerType.getCtorImplementedInterfaces()) {
propType = iface.getPropertyType(propName);
if (propType instanceof FunctionType) {
return (FunctionType) propType;
}
}
}
return null;
}
/**
* Gets an enum type. If the definition is correct, the object literal used
* to define the enum is traversed to gather the elements name, and this
* method checks for duplicates. This method also enforces that all
* elements' name be syntactic constants according to the
* {@link CodingConvention} used.
*
* @param name the enum's name such as {@code HELLO} or {@code goog.foo.BAR}
* @param value the enum's original value. This value may be {@code null}.
* @param parent the value's parent
* @param elementsType the type of the elements of this enum
* @return the enum type
*/
private EnumType getEnumType(String name, Node parent,
Node value, JSType elementsType) {
EnumType enumType = null;
// no value with @enum
if (value != null) {
if (value.getType() == Token.OBJECTLIT) {
// collect enum elements
enumType = typeRegistry.createEnumType(name, elementsType);
// populate the enum type.
Node key = value.getFirstChild();
while (key != null) {
String keyName = NodeUtil.getStringValue(key);
if (enumType.hasOwnProperty(keyName)) {
compiler.report(JSError.make(sourceName, key, ENUM_DUP, keyName));
} else if (!codingConvention.isValidEnumKey(keyName)) {
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else {
enumType.defineElement(keyName);
}
key = key.getNext();
}
} else if (value.isQualifiedName()) {
Var var = scope.getVar(value.getQualifiedName());
if (var != null && var.getType() instanceof EnumType) {
enumType = (EnumType) var.getType();
}
}
}
if (enumType == null) {
compiler.report(JSError.make(sourceName, parent, ENUM_INITIALIZER));
} else if (scope.isGlobal()) {
if (name != null && !name.isEmpty()) {
typeRegistry.declareType(name, enumType.getElementsType());
}
}
return enumType;
}
/**
* Defines a typed variable.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> The defining node will be annotated with the
* variable's type or {@code null} if its type is inferred.
* @param name the defining node. It must be a {@link Token#NAME}.
* @param parent the {@code name}'s parent.
* @param type the variable's type. It may be {@code null}, in which case
* the variable's type will be inferred.
*/
private void defineSlot(Node name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualfied names.
boolean shouldDeclareOnGlobalThis = false;
if (n.getType() == Token.NAME) {
Preconditions.checkArgument(
parent.getType() == Token.FUNCTION ||
parent.getType() == Token.VAR ||
parent.getType() == Token.LP ||
parent.getType() == Token.CATCH);
shouldDeclareOnGlobalThis = scope.isGlobal() &&
(parent.getType() == Token.VAR ||
parent.getType() == Token.FUNCTION);
} else {
Preconditions.checkArgument(
n.getType() == Token.GETPROP &&
(parent.getType() == Token.ASSIGN ||
parent.getType() == Token.EXPR_RESULT));
}
String variableName = n.getQualifiedName();
Preconditions.checkArgument(!variableName.isEmpty());
// If n is a property, then we should really declare it in the
// scope where the root object appears. This helps out people
// who declare "global" names in an anonymous namespace.
Scope scopeToDeclareIn = scope;
if (n.getType() == Token.GETPROP && !scope.isGlobal() &&
isQnameRootedInGlobalScope(n)) {
Scope globalScope = scope.getGlobalScope();
// don't try to declare in the global scope if there's
// already a symbol there with this name.
if (!globalScope.isDeclared(variableName, false)) {
scopeToDeclareIn = scope.getGlobalScope();
}
}
// declared in closest scope?
if (scopeToDeclareIn.isDeclared(variableName, false)) {
Var oldVar = scopeToDeclareIn.getVar(variableName);
validator.expectUndeclaredVariable(
sourceName, n, parent, oldVar, variableName, type);
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
} else {
if (!inferred) {
setDeferredType(n, type);
}
CompilerInput input = compiler.getInput(sourceName);
boolean isExtern = input.isExtern();
Var newVar =
scopeToDeclareIn.declare(variableName, n, type, input, inferred);
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS);
if (inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
getNativeType(JSTypeNative.NO_TYPE) :
type,
isExtern);
} else {
globalThis.defineDeclaredProperty(variableName, type, isExtern);
}
}
// We need to do some additional work for constructors and interfaces.
if (type instanceof FunctionType &&
// We don't want to look at empty function types.
!type.isEmptyType()) {
FunctionType fnType = (FunctionType) type;
if ((fnType.isConstructor() || fnType.isInterface()) &&
!fnType.equals(getNativeType(U2U_CONSTRUCTOR_TYPE))) {
// Declare var.prototype in the scope chain.
FunctionType superClassCtor = fnType.getSuperClassConstructor();
scopeToDeclareIn.declare(variableName + ".prototype", n,
fnType.getPrototype(), input,
/* declared iff there's an explicit supertype */
superClassCtor == null ||
superClassCtor.getInstanceType().equals(
getNativeType(OBJECT_TYPE)));
// Make sure the variable is initialized to something.
if (newVar.getInitialValue() == null && !isExtern) {
compiler.report(
JSError.make(sourceName, n,
fnType.isConstructor() ?
CTOR_INITIALIZER : IFACE_INITIALIZER,
variableName));
}
}
}
}
}
/**
* Check if the given node is a property of a name in the global scope.
*/
private boolean isQnameRootedInGlobalScope(Node n) {
Node root = NodeUtil.getRootOfQualifiedName(n);
if (root.getType() == Token.NAME) {
Var var = scope.getVar(root.getString());
if (var != null) {
return var.isGlobal();
}
}
return false;
}
/**
* Look for a type declaration on a GETPROP node.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
private JSType getDeclaredGetPropType(NodeTraversal t, JSDocInfo info,
Node n, @Nullable Node rhsValue) {
if (info != null && info.hasType()) {
return getDeclared
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>TypeInAnnotation(t, n, info);
} else if (info != null && info.hasEnumParameterType()) {
return n.getJSType();
} else if (rhsValue != null &&
rhsValue.getType() == Token.FUNCTION) {
return rhsValue.getJSType();
} else {
return getDeclaredTypeInAnnotation(t, n, info);
}
}
/**
* Look for class-defining calls.
* Because JS has no 'native' syntax for defining classes,
* this is often very coding-convention dependent and business-logic heavy.
*/
private void checkForClassDefiningCalls(
NodeTraversal t, Node n, Node parent) {
SubclassRelationship relationship =
codingConvention.getClassesDefinedByCall(n);
if (relationship != null) {
ObjectType superClass = ObjectType.cast(
typeRegistry.getType(relationship.superclassName));
ObjectType subClass = ObjectType.cast(
typeRegistry.getType(relationship.subclassName));
if (superClass != null && subClass != null) {
FunctionType superCtor = superClass.getConstructor();
FunctionType subCtor = subClass.getConstructor();
if (relationship.type == SubclassType.INHERITS) {
validator.expectSuperType(t, n, superClass, subClass);
}
if (superCtor != null && subCtor != null) {
codingConvention.applySubclassRelationship(
superCtor, subCtor, relationship.type);
}
}
}
String singletonGetterClassName =
codingConvention.getSingletonGetterClassName(n);
if (singletonGetterClassName != null) {
ObjectType objectType = ObjectType.cast(
typeRegistry.getType(singletonGetterClassName));
if (objectType != null) {
FunctionType functionType = objectType.getConstructor();
if (functionType != null) {
FunctionType getterType =
typeRegistry.createFunctionType(objectType);
codingConvention.applySingletonGetter(functionType, getterType,
objectType);
}
}
}
DelegateRelationship delegateRelationship =
codingConvention.getDelegateRelationship(n);
if (delegateRelationship != null) {
applyDelegateRelationship(delegateRelationship);
}
ObjectLiteralCast objectLiteralCast =
codingConvention.getObjectLiteralCast(t, n);
if (objectLiteralCast != null) {
ObjectType type = ObjectType.cast(
typeRegistry.getType(objectLiteralCast.typeName));
if (type != null && type.getConstructor() != null) {
setDeferredType(objectLiteralCast.objectNode, type);
} else {
compiler.report(JSError.make(t.getSourceName(), n,
CONSTRUCTOR_EXPECTED));
}
}
}
/**
* Apply special properties that only apply to delegates.
*/
private void applyDelegateRelationship(
DelegateRelationship delegateRelationship) {
ObjectType delegatorObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegator));
ObjectType delegateBaseObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegateBase));
ObjectType delegate
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>SuperObject = ObjectType.cast(
typeRegistry.getType(codingConvention.getDelegateSuperclassName()));
if (delegatorObject != null &&
delegateBaseObject != null &&
delegateSuperObject != null) {
FunctionType delegatorCtor = delegatorObject.getConstructor();
FunctionType delegateBaseCtor = delegateBaseObject.getConstructor();
FunctionType delegateSuperCtor = delegateSuperObject.getConstructor();
if (delegatorCtor != null && delegateBaseCtor != null &&
delegateSuperCtor != null) {
FunctionParamBuilder functionParamBuilder =
new FunctionParamBuilder(typeRegistry);
functionParamBuilder.addRequiredParams(
getNativeType(U2U_CONSTRUCTOR_TYPE));
FunctionType findDelegate = typeRegistry.createFunctionType(
typeRegistry.createDefaultObjectUnion(delegateBaseObject),
functionParamBuilder.build());
FunctionType delegateProxy = typeRegistry.createConstructorType(
delegateBaseObject.getReferenceName() + DELEGATE_PROXY_SUFFIX,
null, null, null);
delegateProxy.setPrototypeBasedOn(delegateBaseObject);
codingConvention.applyDelegateRelationship(
delegateSuperObject, delegateBaseObject, delegatorObject,
delegateProxy, findDelegate);
delegateProxyPrototypes.add(delegateProxy.getPrototype());
}
}
}
/**
* Declare the symbol for a qualified name in the global scope.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param parent The parent of {@code n}.
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
Node ownerNode = n.getFirstChild();
String ownerName = ownerNode.getQualifiedName();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
Preconditions.checkArgument(qName != null && ownerName != null);
// Function prototypes are special.
// It's a common JS idiom to do:
// F.prototype = { ... };
// So if F does not have an explicitly declared super type,
// allow F.prototype to be redefined arbitrarily.
if ("prototype".equals(propName)) {
Var qVar = scope.getVar(qName);
if (qVar != null) {
if (!qVar.isTypeInferred()) {
// Just ignore assigns to declared prototypes.
return;
}
if (qVar.getScope() == scope) {
scope.undeclare(qVar);
}
}
}
// Precedence of type information on GETPROPs:
// 1) @type annnotation / @enum annotation
// 2) ASSIGN to FUNCTION literal
// 3) @param/@return annotation (with no function literal)
// 4
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>) ASSIGN to anything else
//
// 1 and 3 are declarations, 4 is inferred, and 2 is a declaration iff
// the function has not been declared before.
//
// FUNCTION literals are special because TypedScopeCreator is very smart
// about getting as much type information as possible for them.
// Determining type for #1 + #2 + #3
JSType valueType = getDeclaredGetPropType(t, info, n, rhsValue);
if (valueType == null && rhsValue != null) {
// Determining type for #4
valueType = rhsValue.getJSType();
}
if (valueType == null) {
if (parent.getType() == Token.EXPR_RESULT) {
stubDeclarations.add(new StubDeclaration(
n,
t.getInput() != null && t.getInput().isExtern(),
ownerName));
}
return;
}
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3
inferred = !(info.hasType() || info.hasEnumParameterType() ||
FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred) {
// Determining declaration for #2
inferred = !(rhsValue != null &&
rhsValue.getType() == Token.FUNCTION &&
!scope.isDeclared(qName, false));
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
ownerType.defineDeclaredProperty(propName, valueType, isExtern);
}
}
// If the property is already declared, the error will be
// caught when we try to declare it in the current scope.
defineSlot(n, parent, valueType, inferred);
} else if (rhsValue != null &&
rhsValue.getType() == Token.TRUE) {
// We declare these for delegate proxy method properties.
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType instanceof FunctionType) {
JSType ownerTypeOfThis = ((FunctionType) ownerType).getTypeOfThis();
String delegateName = codingConvention.getDelegateSuperclassName();
JSType delegateType = delegateName == null ?
null : typeRegistry.getType(delegateName);
if (delegateType != null &&
ownerTypeOfThis.isSubtype(delegateType)) {
defineSlot(n, parent, getNativeType(BOOLEAN_TYPE),
true);
}
}
}
}
/**
* Find the ObjectType associated with the given slot.
* @param slot
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Name The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub delcarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
ObjectType unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, isExtern);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final ObjectType thisType;
CollectProperties(ObjectType thisType) {
this.thisType = thisType;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.EXPR_RESULT) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(t, child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(t, child, child, null);
break;
}
}
}
private void maybeCollectMember(NodeTraversal t,
Node member, Node nodeWithJsDocInfo, @Nullable Node value) {
JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo();
// Do nothing if there is no JSDoc type info, or
// if the node is not a member expression, or
// if the member expression is not of the form: this.someProperty.
if (
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>info == null ||
member.getType() != Token.GETPROP ||
member.getFirstChild().getType() != Token.THIS) {
return;
}
member.getFirstChild().setJSType(thisType);
JSType jsType = getDeclaredGetPropType(t, info, member, value);
Node name = member.getLastChild();
if (jsType != null &&
(name.getType() == Token.NAME || name.getType() == Token.STRING)) {
thisType.defineDeclaredProperty(
name.getString(),
jsType,
false /* functions with implementations are not in externs */);
}
}
} // end CollectProperties
}
/**
* A stub declaration without any type information.
*/
private static final class StubDeclaration {
private final Node node;
private final boolean isExtern;
private final String ownerName;
private StubDeclaration(Node node, boolean isExtern, String ownerName) {
this.node = node;
this.isExtern = isExtern;
this.ownerName = ownerName;
}
}
/**
* A shallow traversal of the global scope to build up all classes,
* functions, and methods.
*/
private final class GlobalScopeBuilder extends AbstractScopeBuilder {
private GlobalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Visit a node in the global scope, and add anything it declares to the
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.ASSIGN:
// Handle typedefs.
checkForOldStyleTypedef(t, n);
break;
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild()) {
checkForOldStyleTypedef(t, n);
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> out on recusive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE));
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.getType() == Token.GETPROP) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate An ASSIGN or VAR node.
*/
// TODO(nicksantos): Kill this.
private void checkForOldStyleTypedef(NodeTraversal t, Node candidate) {
// old-style typedefs
String typedef = codingConvention.identifyTypeDefAssign(candidate);
if (typedef != null) {
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recusive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE));
JSDocInfo info = candidate.getJSDocInfo();
JSType realType = null;
if (info != null && info.getType() != null) {
realType = info.getType().evaluate(scope, typeRegistry);
}
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
// Duplicate typedefs get handled when we try to register
// this typedef in the scope.
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're builidng.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) return;
if (n.getType() == Token.LP && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> "\"{0}\" is not a valid JS property name");
static final DiagnosticType XMODULE_REQUIRE_ERROR = DiagnosticType.warning(
"JSC_XMODULE_REQUIRE_ERROR",
"namespace \"{0}\" provided in module {1} " +
"but required in module {2}");
static final DiagnosticType NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR =
DiagnosticType.error(
"JSC_NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR",
"goog.setCssNameMapping only takes an object literal with string values");
static final DiagnosticType BASE_CLASS_ERROR = DiagnosticType.error(
"JSC_BASE_CLASS_ERROR",
"incorrect use of goog.base: {0}");
/** The root Closure namespace */
static final String GOOG = "goog";
private final AbstractCompiler compiler;
private final JSModuleGraph moduleGraph;
// The goog.provides must be processed in a deterministic order.
private final Map<String, ProvidedName> providedNames =
Maps.newTreeMap();
private final List<UnrecognizedRequire> unrecognizedRequires =
Lists.newArrayList();
private final Set<String> exportedVariables = Sets.newHashSet();
private final CheckLevel requiresLevel;
private final boolean rewriteNewDateGoogNow;
ProcessClosurePrimitives(AbstractCompiler compiler,
CheckLevel requiresLevel,
boolean rewriteNewDateGoogNow) {
this.compiler = compiler;
this.moduleGraph = compiler.getModuleGraph();
this.requiresLevel = requiresLevel;
this.rewriteNewDateGoogNow = rewriteNewDateGoogNow;
// goog is special-cased because it is provided in Closure's base library.
providedNames.put(GOOG,
new ProvidedName(GOOG, null, null, false /* implicit */));
}
Set<String> getExportedVariableNames() {
return exportedVariables;
}
@Override
public void process(Node externs, Node root) {
new NodeTraversal(compiler, this).traverse(root);
for (ProvidedName pn : providedNames.values()) {
pn.replace();
}
if (requiresLevel.isOn()) {
for (UnrecognizedRequire r : unrecognizedRequires) {
DiagnosticType error;
ProvidedName expectedName = providedNames.get(r.namespace);
if (expectedName != null && expectedName.firstNode != null) {
// The namespace ended up getting provided after it was required.
error = LATE_PROVIDE_ERROR;
} else {
error = MISSING_PROVIDE_ERROR;
}
compiler.report(JSError.make(
r.inputName, r.requireNode, requiresLevel, error, r.namespace));
}
}
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
boolean isExpr = parent.getType() == Token.EXPR_RESULT;
Node left = n
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.getFirstChild();
if (left.getType() == Token.GETPROP) {
Node name = left.getFirstChild();
if (name.getType() == Token.NAME &&
GOOG.equals(name.getString())) {
// For the sake of simplicity, we report code changes
// when we see a provides/requires, and don't worry about
// reporting the change when we actually do the replacement.
String methodName = name.getNext().getString();
if ("base".equals(methodName)) {
processBaseClassCall(t, n);
} else if (!isExpr) {
// All other methods must be called in an EXPR.
break;
} else if ("require".equals(methodName)) {
processRequireCall(t, n, parent);
} else if ("provide".equals(methodName)) {
processProvideCall(t, n, parent);
} else if ("exportSymbol".equals(methodName)) {
Node arg = left.getNext();
if (arg.getType() == Token.STRING) {
int dot = arg.getString().indexOf('.');
if (dot == -1) {
exportedVariables.add(arg.getString());
} else {
exportedVariables.add(arg.getString().substring(0, dot));
}
}
} else if ("addDependency".equals(methodName)) {
CodingConvention convention = compiler.getCodingConvention();
List<String> typeDecls =
convention.identifyTypeDeclarationCall(n);
if (typeDecls != null) {
for (String typeDecl : typeDecls) {
compiler.getTypeRegistry().forwardDeclareType(typeDecl);
}
}
// We can't modify parent, so just create a node that will
// get compiled out.
parent.replaceChild(n, Node.newNumber(0));
compiler.reportCodeChange();
} else if ("setCssNameMapping".equals(methodName)) {
processSetCssNameMapping(t, n, parent);
}
}
}
break;
case Token.ASSIGN:
case Token.NAME:
// If this is an assignment to a provided name, remove the provided
// object.
handleCandidateProvideDefinition(t, n, parent);
break;
case Token.FUNCTION:
// If this is a declaration of a provided named function, this is an
// error. Hosited functions will explode if the're provided.
if (t.inGlobalScope() &&
!NodeUtil.isFunctionExpression(n)) {
String name = n.getFirstChild().getString();
ProvidedName pn = providedNames.get(name);
if (pn != null) {
compiler.report(t.makeError(n, FUNCTION_NAMESPACE_ERROR, name));
}
}
break;
case Token.NEW:
trySimplifyNewDate(t, n, parent);
break;
case Token.GETPROP:
if (n.getFirstChild().getType() == Token.NAME &&
parent.getType() != Token.CALL &&
parent.getType() != Token.ASSIGN &&
"
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>goog.base".equals(n.getQualifiedName())) {
reportBadBaseClassUse(t, n, "May only be called directly.");
}
break;
}
}
/**
* Handles a goog.require call.
*/
private void processRequireCall(NodeTraversal t, Node n, Node parent) {
Node left = n.getFirstChild();
Node arg = left.getNext();
if (verifyArgument(t, left, arg)) {
String ns = arg.getString();
ProvidedName provided = providedNames.get(ns);
if (provided == null || !provided.isExplicitlyProvided()) {
unrecognizedRequires.add(
new UnrecognizedRequire(n, ns, t.getSourceName()));
} else {
JSModule providedModule = provided.explicitModule;
// This must be non-null, because there was an explicit provide.
Preconditions.checkNotNull(providedModule);
JSModule module = t.getModule();
if (moduleGraph != null &&
module != providedModule &&
!moduleGraph.dependsOn(module, providedModule)) {
compiler.report(
t.makeError(n, XMODULE_REQUIRE_ERROR, ns,
providedModule.getName(),
module.getName()));
}
}
// Requires should be removed before runtime. The one
// exception is if the type has not been provided yet and
// errors for broken requires are turned off, in which case,
// we will be doing a later pass that may error, so we can
// leave this here this time and let it error next time if it
// is still not provided.
if (provided != null || requiresLevel.isOn()) {
parent.detachFromParent();
compiler.reportCodeChange();
}
}
}
/**
* Handles a goog.provide call.
*/
private void processProvideCall(NodeTraversal t, Node n, Node parent) {
Node left = n.getFirstChild();
Node arg = left.getNext();
if (verifyProvide(t, left, arg)) {
String ns = arg.getString();
if (providedNames.containsKey(ns)) {
ProvidedName previouslyProvided = providedNames.get(ns);
if (!previouslyProvided.isExplicitlyProvided()) {
previouslyProvided.addProvide(parent, t.getModule(), true);
} else {
compiler.report(
t.makeError(n, DUPLICATE_NAMESPACE_ERROR, ns));
}
} else {
registerAnyProvidedPrefixes(ns, parent, t.getModule());
providedNames.put(
ns, new ProvidedName(ns, parent, t.getModule(), true));
}
}
}
/**
* Handles a candidate definition for a goog.provided name.
*/
private void handleCandidateProvideDefinition(
NodeTraversal t, Node n, Node parent) {
if (t.inGlobalScope()) {
String name = null;
if (n.getType() == Token.NAME && parent.getType() == Token.VAR) {
name = n.getString();
} else if
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> (n.getType() == Token.ASSIGN &&
parent.getType() == Token.EXPR_RESULT) {
name = n.getFirstChild().getQualifiedName();
}
if (name != null) {
if (parent.getBooleanProp(Node.IS_NAMESPACE)) {
processProvideFromPreviousPass(t, name, parent);
} else {
ProvidedName pn = providedNames.get(name);
if (pn != null) {
pn.addDefinition(parent, t.getModule());
}
}
}
}
}
/**
* Processes the base class call.
*/
private void processBaseClassCall(NodeTraversal t, Node n) {
// Two things must hold for every goog.base call:
// 1) We must be calling it on "this".
// 2) We must be calling it on a prototype method of the same name as
// the one we're in, OR we must be calling it from a constructor.
// If both of those things are true, then we can rewrite:
// <pre>
// function Foo() {
// goog.base(this);
// }
// goog.inherits(Foo, BaseFoo);
// Foo.prototype.bar = function() {
// goog.base(this, 'bar', 1);
// };
// </pre>
// as the easy-to-optimize:
// <pre>
// function Foo() {
// BaseFoo.call(this);
// }
// goog.inherits(Foo, BaseFoo);
// Foo.prototype.bar = function() {
// Foo.superClass_.bar.call(this, 1);
// };
//
// Most of the logic here is just to make sure the AST's
// structure is what we expect it to be.
Node callee = n.getFirstChild();
Node thisArg = callee.getNext();
if (thisArg == null || thisArg.getType() != Token.THIS) {
reportBadBaseClassUse(t, n, "First argument must be 'this'.");
return;
}
Node enclosingFnNameNode = getEnclosingDeclNameNode(t);
if (enclosingFnNameNode == null) {
reportBadBaseClassUse(t, n, "Could not find enclosing method.");
return;
}
String enclosingQname = enclosingFnNameNode.getQualifiedName();
if (enclosingQname.indexOf(".prototype.") == -1) {
// Handle constructors.
Node enclosingParent = enclosingFnNameNode.getParent();
Node maybeInheritsExpr = (enclosingParent.getType() == Token.ASSIGN ?
enclosingParent.getParent() : enclosingParent).getNext();
Node baseClassNode = null;
if (maybeInheritsExpr != null &&
maybeInheritsExpr.getType() == Token.EXPR_RESULT &&
maybeInheritsExpr.getFirstChild().getType() == Token.CALL) {
Node callNode = maybeInheritsExpr.getFirstChild();
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> if ("goog.inherits".equals(
callNode.getFirstChild().getQualifiedName()) &&
callNode.getLastChild().isQualifiedName()) {
baseClassNode = callNode.getLastChild();
}
}
if (baseClassNode == null) {
reportBadBaseClassUse(
t, n, "Could not find goog.inherits for base class");
return;
}
// We're good to go.
n.replaceChild(
callee,
NodeUtil.newQualifiedNameNode(
compiler.getCodingConvention(),
String.format("%s.call", baseClassNode.getQualifiedName()),
callee, "goog.base"));
compiler.reportCodeChange();
} else {
// Handle methods.
Node methodNameNode = thisArg.getNext();
if (methodNameNode == null || methodNameNode.getType() != Token.STRING) {
reportBadBaseClassUse(t, n, "Second argument must name a method.");
return;
}
String methodName = methodNameNode.getString();
String ending = ".prototype." + methodName;
if (enclosingQname == null ||
!enclosingQname.endsWith(ending)) {
reportBadBaseClassUse(
t, n, "Enclosing method does not match " + methodName);
return;
}
// We're good to go.
Node className =
enclosingFnNameNode.getFirstChild().getFirstChild();
n.replaceChild(
callee,
NodeUtil.newQualifiedNameNode(
compiler.getCodingConvention(),
String.format("%s.superClass_.%s.call",
className.getQualifiedName(), methodName),
callee, "goog.base"));
n.removeChild(methodNameNode);
compiler.reportCodeChange();
}
}
/**
* Returns the qualified name node of the function whose scope we're in,
* or null if it cannot be found.
*/
private Node getEnclosingDeclNameNode(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
if (NodeUtil.isFunctionDeclaration(scopeRoot)) {
// function x() {...}
return scopeRoot.getFirstChild();
} else {
Node parent = scopeRoot.getParent();
if (parent != null) {
if (parent.getType() == Token.ASSIGN ||
parent.getLastChild() == scopeRoot &&
parent.getFirstChild().isQualifiedName()) {
// x.y.z = function() {...};
return parent.getFirstChild();
} else if (parent.getType() == Token.NAME) {
// var x = function() {...};
return parent;
}
}
}
return null;
}
/** Reports an incorrect use of super-method calling. */
private void reportBadBaseClassUse(
NodeTraversal t, Node n, String extraMessage) {
compiler.report(t.makeError(n, BASE_CLASS_ERROR, extraMessage));
}
/**
* Processes the output of processed-provide from a previous pass. This will
* update our data structures in the same manner as
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> if the provide had been
* processed in this pass.
*/
private void processProvideFromPreviousPass(
NodeTraversal t, String name, Node parent) {
if (!providedNames.containsKey(name)) {
// Record this provide created on a previous pass, and create a dummy
// EXPR node as a placeholder to simulate an explicit provide.
Node expr = new Node(Token.EXPR_RESULT);
expr.copyInformationFromForTree(parent);
parent.getParent().addChildBefore(expr, parent);
compiler.reportCodeChange();
JSModule module = t.getModule();
registerAnyProvidedPrefixes(name, expr, module);
ProvidedName provided = new ProvidedName(name, expr, module, true);
providedNames.put(name, provided);
provided.addDefinition(parent, module);
} else {
// Remove this provide if it came from a previous pass since we have an
// replacement already.
if (isNamespacePlaceholder(parent)) {
parent.getParent().removeChild(parent);
compiler.reportCodeChange();
}
}
}
/**
* Processes a call to goog.setCssNameMapping(). Either the argument to
* goog.setCssNameMapping() is valid, in which case it will be used to create
* a CssRenamingMap for the compiler of this CompilerPass, or it is invalid
* and a JSCompiler error will be reported.
* @see #visit(NodeTraversal, Node, Node)
*/
private void processSetCssNameMapping(NodeTraversal t, Node n, Node parent) {
Node left = n.getFirstChild();
Node arg = left.getNext();
if (verifyArgument(t, left, arg, Token.OBJECTLIT)) {
// Translate OBJECTLIT into SubstitutionMap. All keys and
// values must be strings, or an error will be thrown.
final Map<String, String> cssNames = Maps.newHashMap();
JSError error = null;
for (Node key = arg.getFirstChild(); key != null;
key = key.getNext()) {
Node value = key.getFirstChild();
if (key.getType() != Token.STRING
|| value == null
|| value.getType() != Token.STRING) {
error = t.makeError(n,
NON_STRING_PASSED_TO_SET_CSS_NAME_MAPPING_ERROR);
}
if (error != null) {
compiler.report(error);
break;
}
cssNames.put(key.getString(), value.getString());
}
// If there were no errors, create a CssRenamingMap from cssNames, update
// the compiler to use it and remove the call to goog.setCssNameMapping().
if (error == null) {
CssRenamingMap cssRenamingMap = new CssRenamingMap() {
public String get(String value) {
if (cssNames.containsKey(value)) {
return cssNames.get(value);
} else {
return value;
}
}
};
compiler.setCssRenamingMap
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>(cssRenamingMap);
parent.getParent().removeChild(parent);
compiler.reportCodeChange();
}
}
}
/**
* Try to simplify "new Date(goog.now())" to "new Date()".
*/
private void trySimplifyNewDate(NodeTraversal t, Node n, Node parent) {
if (!rewriteNewDateGoogNow) {
return;
}
Preconditions.checkArgument(n.getType() == Token.NEW);
Node date = n.getFirstChild();
if (!NodeUtil.isName(date) || !"Date".equals(date.getString())) {
return;
}
Node callGoogNow = date.getNext();
if (callGoogNow == null || !NodeUtil.isCall(callGoogNow) ||
callGoogNow.getNext() != null) {
return;
}
Node googNow = callGoogNow.getFirstChild();
String googNowQName = googNow.getQualifiedName();
if (googNowQName == null || !"goog.now".equals(googNowQName)
|| googNow.getNext() != null) {
return;
}
n.removeChild(callGoogNow);
compiler.reportCodeChange();
}
/**
* Verifies that a provide method call has exactly one argument,
* and that it's a string literal and that the contents of the string are
* valid JS tokens. Reports a compile error if it doesn't.
*
* @return Whether the argument checked out okay
*/
private boolean verifyProvide(NodeTraversal t, Node methodName, Node arg) {
if (!verifyArgument(t, methodName, arg)) {
return false;
}
for (String part : arg.getString().split("\\.")) {
if (!NodeUtil.isValidPropertyName(part)) {
compiler.report(t.makeError(arg, INVALID_PROVIDE_ERROR, part));
return false;
}
}
return true;
}
/**
* Verifies that a method call has exactly one argument, and that it's a
* string literal. Reports a compile error if it doesn't.
*
* @return Whether the argument checked out okay
*/
private boolean verifyArgument(NodeTraversal t, Node methodName, Node arg) {
return verifyArgument(t, methodName, arg, Token.STRING);
}
/**
* Verifies that a method call has exactly one argument, and that it is of the
* desired type. Reports a compile error if it doesn't.
*
* @return Whether the argument checked out okay
*/
private boolean verifyArgument(NodeTraversal t, Node methodName, Node arg,
int desiredType) {
DiagnosticType diagnostic = null;
if (arg == null) {
diagnostic = NULL_ARGUMENT_ERROR;
} else if (arg.getType() != desiredType) {
diagnostic = INVALID_ARGUMENT_ERROR;
} else if (arg.getNext() != null) {
diagnostic = TOO_MANY_ARGUMENTS_ERROR;
}
if (diagnostic != null) {
compiler.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>report(
t.makeError(methodName,
diagnostic, methodName.getQualifiedName()));
return false;
}
return true;
}
/**
* Registers ProvidedNames for prefix namespaces if they haven't
* already been defined. The prefix namespaces must be registered in
* order from shortest to longest.
*
* @param ns The namespace whose prefixes may need to be provided.
* @param node The EXPR of the provide call.
* @param module The current module.
*/
private void registerAnyProvidedPrefixes(
String ns, Node node, JSModule module) {
int pos = ns.indexOf('.');
while (pos != -1) {
String prefixNs = ns.substring(0, pos);
pos = ns.indexOf('.', pos + 1);
if (providedNames.containsKey(prefixNs)) {
providedNames.get(prefixNs).addProvide(
node, module, false /* implicit */);
} else {
providedNames.put(
prefixNs,
new ProvidedName(prefixNs, node, module, false /* implicit */));
}
}
}
// -------------------------------------------------------------------------
/**
* Information required to replace a goog.provide call later in the traversal.
*/
private class ProvidedName {
private final String namespace;
// The node and module where the call was explicitly or implicitly
// goog.provided.
private final Node firstNode;
private final JSModule firstModule;
// The node where the call was explicitly goog.provided. May be null
// if the namespace is always provided implicitly.
private Node explicitNode = null;
private JSModule explicitModule = null;
// The candidate definition.
private Node candidateDefinition = null;
// The minimum module where the provide must appear.
private JSModule minimumModule = null;
// The replacement declaration.
private Node replacementNode = null;
ProvidedName(String namespace, Node node, JSModule module,
boolean explicit) {
Preconditions.checkArgument(
node == null /* The base case */ ||
NodeUtil.isExpressionNode(node));
this.namespace = namespace;
this.firstNode = node;
this.firstModule = module;
addProvide(node, module, explicit);
}
/**
* Add an implicit or explicit provide.
*/
void addProvide(Node node, JSModule module, boolean explicit) {
if (explicit) {
Preconditions.checkState(explicitNode == null);
Preconditions.checkArgument(NodeUtil.isExpressionNode(node));
explicitNode = node;
explicitModule = module;
}
updateMinimumModule(module);
}
boolean isExplicitlyProvided() {
return explicitNode != null;
}
/**
* Record function declaration, variable declaration or assignment that
* refers to the same name as the provide statement. Give preference to
* declarations; if no declation exists record a reference to an
* assignment so it repurposed later.
*/
void addDefinition(Node node, JSModule module) {
Preconditions.checkArgument(NodeUtil.isExpressionNode(node) || //
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> assign
NodeUtil.isFunction(node) ||
NodeUtil.isVar(node));
Preconditions.checkArgument(explicitNode != node);
if ((candidateDefinition == null) || !NodeUtil.isExpressionNode(node)) {
candidateDefinition = node;
updateMinimumModule(module);
}
}
private void updateMinimumModule(JSModule newModule) {
if (minimumModule == null) {
minimumModule = newModule;
} else if (moduleGraph != null) {
minimumModule = moduleGraph.getDeepestCommonDependencyInclusive(
minimumModule, newModule);
} else {
// If there is no module graph, then there must be exactly one
// module in the program.
Preconditions.checkState(newModule == minimumModule,
"Missing module graph");
}
}
/**
* Replace the provide statement.
*
* If we're providing a name with no definition, then create one.
* If we're providing a name with a duplicate definition, then make sure
* that definition becomes a declaration.
*/
void replace() {
if (firstNode == null) {
// Don't touch the base case ('goog').
replacementNode = candidateDefinition;
return;
}
// Handle the case where there is a duplicate definition for an explicitly
// provided symbol.
if (candidateDefinition != null && explicitNode != null) {
explicitNode.detachFromParent();
compiler.reportCodeChange();
// Does this need a VAR keyword?
replacementNode = candidateDefinition;
if (NodeUtil.isExpressionNode(candidateDefinition)) {
candidateDefinition.putBooleanProp(Node.IS_NAMESPACE, true);
Node assignNode = candidateDefinition.getFirstChild();
Node nameNode = assignNode.getFirstChild();
if (nameNode.getType() == Token.NAME) {
// Need to convert this assign to a var declaration.
Node valueNode = nameNode.getNext();
assignNode.removeChild(nameNode);
assignNode.removeChild(valueNode);
nameNode.addChildToFront(valueNode);
Node varNode = new Node(Token.VAR, nameNode);
varNode.copyInformationFrom(candidateDefinition);
candidateDefinition.getParent().replaceChild(
candidateDefinition, varNode);
nameNode.setJSDocInfo(assignNode.getJSDocInfo());
compiler.reportCodeChange();
replacementNode = varNode;
}
}
} else {
// Handle the case where there's not a duplicate definition.
replacementNode = createDeclarationNode();
if (firstModule == minimumModule) {
firstNode.getParent().addChildBefore(replacementNode, firstNode);
} else {
// In this case, the name was implicitly provided by two independent
// modules. We need to move this code up to a common module.
int indexOfDot = namespace.lastIndexOf('.');
if (indexOfDot == -1) {
// Any old place is fine.
compiler.getNodeForCodeInsertion(minimumModule)
.addChildToBack(replacementNode);
} else {
// Add it after the parent
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> namespace.
ProvidedName parentName =
providedNames.get(namespace.substring(0, indexOfDot));
Preconditions.checkNotNull(parentName);
Preconditions.checkNotNull(parentName.replacementNode);
parentName.replacementNode.getParent().addChildAfter(
replacementNode, parentName.replacementNode);
}
}
if (explicitNode != null) {
explicitNode.detachFromParent();
}
compiler.reportCodeChange();
}
}
/**
* Create the declaration node for this name, without inserting it
* into the AST.
*/
private Node createDeclarationNode() {
if (namespace.indexOf('.') == -1) {
return makeVarDeclNode(namespace, firstNode);
} else {
return makeAssignmentExprNode(namespace, firstNode);
}
}
/**
* Creates a simple namespace variable declaration
* (e.g. <code>var foo = {};</code>).
*
* @param namespace A simple namespace (must be a valid js identifier)
* @param sourceNode The node to get source information from.
*/
private Node makeVarDeclNode(String namespace, Node sourceNode) {
Node name = Node.newString(Token.NAME, namespace);
name.addChildToFront(createNamespaceLiteral());
Node decl = new Node(Token.VAR, name);
decl.putBooleanProp(Node.IS_NAMESPACE, true);
// TODO(nicksantos): ew ew ew. Create a mutator package.
if (compiler.getCodingConvention().isConstant(namespace)) {
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
Preconditions.checkState(isNamespacePlaceholder(decl));
decl.copyInformationFromForTree(sourceNode);
return decl;
}
/**
* There are some special cases where clients of the compiler
* do not run TypedScopeCreator after running this pass.
* So always give the namespace literal a type.
*/
private Node createNamespaceLiteral() {
Node objlit = new Node(Token.OBJECTLIT);
objlit.setJSType(
compiler.getTypeRegistry().createAnonymousObjectType());
return objlit;
}
/**
* Creates a dotted namespace assignment expression
* (e.g. <code>foo.bar = {};</code>).
*
* @param namespace A dotted namespace
* @param node A node from which to copy source info.
*/
private Node makeAssignmentExprNode(String namespace, Node node) {
Node decl = new Node(Token.EXPR_RESULT,
new Node(Token.ASSIGN,
NodeUtil.newQualifiedNameNode(
compiler.getCodingConvention(), namespace, node, namespace),
createNamespaceLiteral()));
decl.putBooleanProp(Node.IS_NAMESPACE, true);
Preconditions.checkState(isNamespacePlaceholder(decl));
decl.copyInformationFromForTree(node);
return decl;
}
}
/**
* @return Whether the node is namespace placeholder.
*/
private static boolean isNamespacePlaceholder(Node n) {
if (!n
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>.getBooleanProp(Node.IS_NAMESPACE)) {
return false;
}
Node value = null;
if (n.getType() == Token.EXPR_RESULT) {
Node assign = n.getFirstChild();
value = assign.getLastChild();
} else if (n.getType() == Token.VAR) {
Node name = n.getFirstChild();
value = name.getFirstChild();
}
return value != null
&& value.getType() == Token.OBJECTLIT
&& !value.hasChildren();
}
// -------------------------------------------------------------------------
/**
* Information required to create a {@code MISSING_PROVIDE_ERROR} warning.
*/
private class UnrecognizedRequire {
final Node requireNode;
final String namespace;
final String inputName;
UnrecognizedRequire(Node requireNode, String namespace, String inputName) {
this.requireNode = requireNode;
this.namespace = namespace;
this.inputName = inputName;
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> can be collapsed, and the
* node traversal may give them to us again)
*/
private final Set<Node> nodesToCollapse = Sets.newHashSet();
CollapseVariableDeclarations(AbstractCompiler compiler) {
Preconditions.checkState(!compiler.isNormalized());
this.compiler = compiler;
}
public void process(Node externs, Node root) {
collapses.clear();
nodesToCollapse.clear();
NodeTraversal.traverse(compiler, root,
new CombinedCompilerPass(compiler,
new ExploitAssigns(), new GatherCollapses()));
if (!collapses.isEmpty()) {
applyCollapses();
compiler.reportCodeChange();
}
}
private class ExploitAssigns extends AbstractPostOrderCallback {
public void visit(NodeTraversal t, Node expr, Node exprParent) {
if (!NodeUtil.isExprAssign(expr)) {
return;
}
collapseAssign(t, expr.getFirstChild(), expr, exprParent);
}
/**
* Try to collapse the given assign into subsequent expressions.
*/
private void collapseAssign(NodeTraversal t, Node assign, Node expr,
Node exprParent) {
Node leftValue = assign.getFirstChild();
Node rightValue = leftValue.getNext();
if (isCollapsibleValue(leftValue, true) &&
collapseAssignEqualTo(expr, exprParent, leftValue)) {
t.getCompiler().reportCodeChange();
} else if (isCollapsibleValue(rightValue, false) &&
collapseAssignEqualTo(expr, exprParent, rightValue)) {
t.getCompiler().reportCodeChange();
} else if (rightValue.getType() == Token.ASSIGN) {
// Recursively deal with nested assigns.
collapseAssign(t, rightValue, expr, exprParent);
}
}
/**
* Determines whether we know enough about the given value to be able
* to collapse it into subsequent expressions.
*
* For example, we can collapse booleans and variable names:
* <code>
* x = 3; y = x; // y = x = 3;
* a = true; b = true; // b = a = true;
* <code>
* But we won't try to collapse complex expressions.
*
* @param value The value node.
* @param isLValue Whether it's on the left-hand side of an expr.
*/
private boolean isCollapsibleValue(Node value, boolean isLValue) {
switch (value.getType()) {
case Token.GETPROP:
// Do not collapse GETPROPs on arbitrary objects, because
// they may be implemented setter functions, and oftentimes
// setter functions fail on native objects. This is ok for "THIS"
// objects, because we assume that they are non-native.
return !isLValue || value.getFirstChild().getType() == Token.THIS;
case Token.NAME:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
case Token.STRING:
return true;
}
return false;
}
/**
* Collapse the given assign expression into the expression directly
* following it, if possible.
*
* @param expr The expression that may be moved.
* @param exprParent The parent of {@code expr}.
* @param value The value of this expression, expressed as a node. Each
* expression may have multiple values, so this function may be called
* multiple times for the same expression. For example,
* <code>
* a = true;
* </code>
* is equal to the name "a" and the boolean "true".
* @return Whether the expression was collapsed succesfully.
*/
private boolean collapseAssignEqualTo(Node expr, Node exprParent,
Node value) {
Node assign = expr.getFirstChild();
Node parent = exprParent;
Node next = expr.getNext();
while (next != null) {
switch (next.getType()) {
case Token.AND:
case Token.OR:
case Token.HOOK:
case Token.IF:
case Token.RETURN:
case Token.EXPR_RESULT:
// Dive down the left side
parent = next;
next = next.getFirstChild();
break;
case Token.VAR:
if (next.getFirstChild().hasChildren()) {
parent = next.getFirstChild();
next = parent.getFirstChild();
break;
}
return false;
case Token.GETPROP:
case Token.NAME:
if (next.isQualifiedName()) {
String nextName = next.getQualifiedName();
if (value.isQualifiedName() &&
nextName.equals(value.getQualifiedName())) {
// If the previous expression evaluates to value of a
// qualified name, and that qualified name is used again
// shortly, then we can exploit the assign here.
// Verify the assignment doesn't change its own value.
if (!isSafeReplacement(next, assign)) {
return false;
}
exprParent.removeChild(expr);
expr.removeChild(assign);
parent.replaceChild(next, assign);
return true;
}
}
return false;
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
if (value.getType() == next.getType()) {
if ((next.getType() == Token.STRING ||
next.getType() == Token.NUMBER) &&
!next.isEquivalentTo(value)) {
return false;
}
// If the r-value of the expr assign is an immutable value,
// and the value is used again shortly, then we can exploit
// the assign here.
exprParent.removeChild(expr);
expr.removeChild(assign);
parent.replaceChild(next, assign);
return true;
}
return false;
case Token.ASSIGN:
// Assigns are really tricky. In lots of cases, we want to inline
// into the right side of the assign.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> But the left side of the
// assign is evaluated first, and it may have convoluted logic:
// a = null;
// (a = b).c = null;
// We don't want to exploit the first assign. Similarly:
// a.b = null;
// a.b.c = null;
// We don't want to exploit the first assign either.
//
// To protect against this, we simply only inline when the left side
// is guaranteed to evaluate to the same L-value no matter what.
Node leftSide = next.getFirstChild();
if (leftSide.getType() == Token.NAME ||
leftSide.getType() == Token.GETPROP &&
leftSide.getFirstChild().getType() == Token.THIS) {
// Dive down the right side of the assign.
parent = next;
next = leftSide.getNext();
break;
} else {
return false;
}
default:
// Return without inlining a thing
return false;
}
}
return false;
}
}
/**
* Checks name referenced in node to determine if it might have
* changed.
* @return Whether the replacement can be made.
*/
private boolean isSafeReplacement(Node node, Node replacement) {
// No checks are needed for simple names.
if (node.getType() == Token.NAME) {
return true;
}
Preconditions.checkArgument(node.getType() == Token.GETPROP);
Node name = node.getFirstChild();
if (name.getType() == Token.NAME
&& isNameAssignedTo(name.getString(), replacement)) {
return false;
}
return true;
}
/**
* @return Whether name is assigned in the expression rooted at node.
*/
private boolean isNameAssignedTo(String name, Node node) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
if (isNameAssignedTo(name, c)) {
return true;
}
}
if (node.getType() == Token.NAME) {
Node parent = node.getParent();
if (parent.getType() == Token.ASSIGN && parent.getFirstChild() == node) {
if (name.equals(node.getString())) {
return true;
}
}
}
return false;
}
/**
* Gathers all of the variable declarations that should be collapsed into one.
* We do not do the collapsing as we go since node traversal would be affected
* by the changes we are making to the parse tree.
*/
private class GatherCollapses extends AbstractPostOrderCallback {
public void visit(NodeTraversal t, Node n, Node parent) {
// Only care about var nodes
if (n.getType() != Token.VAR) return;
// If we've already looked at this node, skip it
if (nodesToCollapse.contains(n)) return;
// Adjacent VAR children of an IF node are the if and else parts and can
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>'t
// be collapsed
if (parent.getType() == Token.IF) return;
Node varNode = n;
// Find variable declarations that follow this one (if any)
n = n.getNext();
boolean hasNodesToCollapse = false;
while (n != null && n.getType() == Token.VAR) {
nodesToCollapse.add(n);
hasNodesToCollapse = true;
n = n.getNext();
}
if (hasNodesToCollapse) {
nodesToCollapse.add(varNode);
collapses.add(new Collapse(varNode, parent));
}
}
}
private void applyCollapses() {
for (Collapse collapse : collapses) {
Node first = collapse.firstVarNode;
while (first.getNext() != null &&
first.getNext().getType() == Token.VAR) {
Node next = collapse.parent.removeChildAfter(first);
// Move all children of the next var node into the first one.
first.addChildrenToBack(next.removeChildren());
}
}
}
}
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>L;
private enum Kind {
ORDINARY,
CONSTRUCTOR,
INTERFACE
}
/**
* {@code [[Call]]} property.
*/
private ArrowType call;
/**
* The {@code prototype} property. This field is lazily initialized by
* {@code #getPrototype()}. The most important reason for lazily
* initializing this field is that there are cycles in the native types
* graph, so some prototypes must temporarily be {@code null} during
* the construction of the graph.
*/
private FunctionPrototypeType prototype;
/**
* Whether a function is a constructor, an interface, or just an ordinary
* function.
*/
private final Kind kind;
/**
* The type of {@code this} in the scope of this function.
*/
private ObjectType typeOfThis;
/**
* The function node which this type represents. It may be {@code null}.
*/
private Node source;
/**
* The interfaces directly implemented by this function.
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> implementedInterfaces = ImmutableList.of();
/**
* The types which are subtypes of this function. It is only relevant for
* constructors and may be {@code null}.
*/
private List<FunctionType> subTypes;
/**
* The template type name. May be {@code null}.
*/
private String templateTypeName;
/** Creates an instance for a function that might be a constructor. */
FunctionType(JSTypeRegistry registry, String name, Node source,
ArrowType arrowType, ObjectType typeOfThis,
String templateTypeName, boolean isConstructor, boolean nativeType) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
nativeType);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkNotNull(arrowType);
this.source = source;
this.kind = isConstructor ? Kind.CONSTRUCTOR : Kind.ORDINARY;
if (isConstructor) {
this.typeOfThis = typeOfThis != null && typeOfThis.isNoObjectType() ?
typeOfThis : new InstanceObjectType(registry, this, nativeType);
} else {
this.typeOfThis = typeOfThis != null ?
typeOfThis :
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
}
this.call = arrowType;
this.templateTypeName = templateTypeName;
}
/** Creates an instance for a function that is an interface. */
private FunctionType(JSTypeRegistry registry, String name, Node source) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE));
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkArgument(name != null);
this.source = source;
this.call = new ArrowType(registry, new Node(Token.LP), null);
this.kind = Kind.INTERFACE;
this.
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
if ("prototype".equals(name)) {
return getPrototype();
} else {
if (!hasOwnProperty(name)) {
if ("call".equals(name)) {
// Define the "call" function lazily.
Node params = getParametersNode();
if (params == null) {
// If there's no params array, don't do any type-checking
// in this CALL function.
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withReturnType(getReturnType())
.build(),
false);
} else {
params = params.cloneTree();
Node thisTypeNode = Node.newString(Token.NAME, "thisType");
thisTypeNode.setJSType(
registry.createOptionalNullableType(getTypeOfThis()));
params.addChildToFront(thisTypeNode);
thisTypeNode.setOptionalArg(true);
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withParamsNode(params)
.withReturnType(getReturnType())
.build(),
false);
}
} else if ("apply".equals(name)) {
// Define the "apply" function lazily.
FunctionParamBuilder builder = new FunctionParamBuilder(registry);
// Ecma-262 says that apply's second argument must be an Array
// or an arguments object. We don't model the arguments object,
// so let's just be forgiving for now.
// TODO(nicksantos): Model the Arguments object.
builder.addOptionalParams(
registry.createNullableType(getTypeOfThis()),
registry.createNullableType(
registry.getNativeType(JSTypeNative.OBJECT_TYPE)));
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withParams(builder)
.withReturnType(getReturnType())
.build(),
false);
}
}
return super.getPropertyType(name);
}
}
@Override
boolean defineProperty(String name, JSType type,
boolean inferred, boolean inExterns) {
if ("prototype".equals(name)) {
ObjectType objType = type.toObjectType();
if (objType != null) {
if (objType.isEquivalentTo(prototype)) {
return true;
}
return setPrototype(
new FunctionPrototypeType(
registry, this, objType, isNativeObjectType()));
} else {
return false;
}
}
return super.defineProperty(name, type, inferred, inExterns);
}
@Override
public boolean isPropertyTypeInferred(String property) {
return "prototype".equals(property) ||
super.isPropertyTypeInferred(property);
}
@Override
public JSType getLeastSupertype(JSType that) {
return supAndInfHelper(that, true);
}
@Override
public JSType getGreatestSubtype(JSType that) {
return supAndInfHelper(that, false);
}
/**
* Computes the supremum or infimum of functions with other types.
* Because sup() and
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>
// broad. The greatest function takes var_args None parameters, which
// means that all parameters register a type warning.
//
// Instead, we use the U2U ctor type, which has unknown type args.
FunctionType greatestFn =
registry.getNativeFunctionType(JSTypeNative.U2U_CONSTRUCTOR_TYPE);
FunctionType leastFn =
registry.getNativeFunctionType(JSTypeNative.LEAST_FUNCTION_TYPE);
return leastSuper ? greatestFn : leastFn;
}
return leastSuper ?
super.getLeastSupertype(that) :
super.getGreatestSubtype(that);
}
/**
* Try to get the sup/inf of two functions by looking at the
* piecewise components.
*/
private FunctionType tryMergeFunctionPiecewise(
FunctionType other, boolean leastSuper) {
Node newParamsNode = null;
if (call.hasEqualParameters(other.call)) {
newParamsNode = call.parameters;
} else {
// If the parameters are not equal, don't try to merge them.
// Someday, we should try to merge the individual params.
return null;
}
JSType newReturnType = leastSuper ?
call.returnType.getLeastSupertype(other.call.returnType) :
call.returnType.getGreatestSubtype(other.call.returnType);
ObjectType newTypeOfThis = null;
if (isEquivalent(typeOfThis, other.typeOfThis)) {
newTypeOfThis = typeOfThis;
} else {
JSType maybeNewTypeOfThis = leastSuper ?
typeOfThis.getLeastSupertype(other.typeOfThis) :
typeOfThis.getGreatestSubtype(other.typeOfThis);
if (maybeNewTypeOfThis instanceof ObjectType) {
newTypeOfThis = (ObjectType) maybeNewTypeOfThis;
} else {
newTypeOfThis = leastSuper ?
registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE) :
registry.getNativeObjectType(JSTypeNative.NO_OBJECT_TYPE);
}
}
boolean newReturnTypeInferred =
call.returnTypeInferred || other.call.returnTypeInferred;
return new FunctionType(
registry, null, null,
new ArrowType(
registry, newParamsNode, newReturnType, newReturnTypeInferred),
newTypeOfThis, null, false, false);
}
/**
* Given a constructor or an interface type, get its superclass constructor
* or {@code null} if none exists.
*/
public FunctionType getSuperClassConstructor() {
Preconditions.checkArgument(isConstructor() || isInterface());
ObjectType maybeSuperInstanceType = getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return null;
}
return maybeSuperInstanceType.getConstructor();
}
/**
* Given a constructor or an interface type, find out whether the unknown
* type is a supertype of the current type.
*/
public boolean hasUnknownSupertype() {
Preconditions.checkArgument(isConstructor() || isInterface());
Preconditions.check
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS>Argument(!this.isUnknownType());
// Potential infinite loop if our type system messes up or someone defines
// a bad type. Otherwise the loop should always end.
FunctionType ctor = this;
while (true) {
ObjectType maybeSuperInstanceType =
ctor.getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return false;
}
if (maybeSuperInstanceType.isUnknownType()) {
return true;
}
ctor = maybeSuperInstanceType.getConstructor();
if (ctor == null) {
return false;
}
Preconditions.checkState(ctor.isConstructor() || ctor.isInterface());
}
}
/**
* Given a constructor or an interface type and a property, finds the
* top-most superclass that has the property defined (including this
* constructor).
*/
public JSType getTopMostDefiningType(String propertyName) {
Preconditions.checkState(isConstructor() || isInterface());
Preconditions.checkArgument(getPrototype().hasProperty(propertyName));
FunctionType ctor = this;
JSType topInstanceType;
do {
topInstanceType = ctor.getInstanceType();
ctor = ctor.getSuperClassConstructor();
} while (ctor != null && ctor.getPrototype().hasProperty(propertyName));
return topInstanceType;
}
/**
* Two function types are equal if their signatures match. Since they don't
* have signatures, two interfaces are equal if their names match.
*/
@Override
public boolean isEquivalentTo(JSType otherType) {
if (!(otherType instanceof FunctionType)) {
return false;
}
FunctionType that = (FunctionType) otherType;
if (!that.isFunctionType()) {
return false;
}
if (this.isConstructor()) {
if (that.isConstructor()) {
return this == that;
}
return false;
}
if (this.isInterface()) {
if (that.isInterface()) {
return this.getReferenceName().equals(that.getReferenceName());
}
return false;
}
if (that.isInterface()) {
return false;
}
return this.typeOfThis.isEquivalentTo(that.typeOfThis) &&
this.call.isEquivalentTo(that.call);
}
@Override
public int hashCode() {
return isInterface() ? getReferenceName().hashCode() : call.hashCode();
}
public boolean hasEqualCallType(FunctionType otherType) {
return this.call.isEquivalentTo(otherType.call);
}
/**
* Informally, a function is represented by
* {@code function (params): returnType} where the {@code params} is a comma
* separated list of types, the first one being a special
* {@code this:T} if the function expects a known type for {@code this}.
*/
@Override
public String toString() {
if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) {
return "Function";
}
StringBuilder b
Closure, 88
<FILEB>
<CHANGES>
Preconditions.checkState(n.getParent().getType() == Token.ASSIGN);
<CHANGEE>
<CHANGES>
Node rhs = n.getNext();
VariableLiveness state = isVariableReadBeforeKill(rhs, variable);
if (state == VariableLiveness.READ) {
return state;
}
<CHANGEE>
<FILEE>
<FILEB>
}
return false;
}
// The current liveness of the variable
private enum VariableLiveness {
MAYBE_LIVE, // May be still live in the current expression tree.
READ, // Known there is a read left of it.
KILL, // Known there is a write before any read.
}
/**
* Give an expression and a variable. It returns READ, if the first
* reference of that variable is a read. It returns KILL, if the first
* reference of that variable is an assignment. It returns MAY_LIVE otherwise.
*/
private VariableLiveness isVariableReadBeforeKill(
Node n, String variable) {
if (NodeUtil.isName(n) && variable.equals(n.getString())) {
if (NodeUtil.isLhs(n, n.getParent())) {
<CHANGES>
<CHANGEE>
// The expression to which the assignment is made is evaluated before
// the RHS is evaluated (normal left to right evaluation) but the KILL
// occurs after the RHS is evaluated.
<CHANGES>
<CHANGEE>
return VariableLiveness.KILL;
} else {
return VariableLiveness.READ;
}
}
// Expressions are evaluated left-right, depth first.
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(child)) { // Not a FUNCTION
VariableLiveness state = isVariableReadBeforeKill(child, variable);
if (state != VariableLiveness.MAYBE_LIVE) {
return state;
<FILEE>
<SCANS> is non-restrictive.
// In practical terms, if C implements I, and I has a method m,
// then any m doesn't necessarily have to C#m's 'this'
// type doesn't need to match I.
(other.typeOfThis.getConstructor() != null &&
other.typeOfThis.getConstructor().isInterface()) ||
// If one of the 'this' types is covariant of the other,
// then we'll treat them as covariant (see comment above).
other.typeOfThis.isSubtype(this.typeOfThis) ||
this.typeOfThis.isSubtype(other.typeOfThis);
return treatThisTypesAsCovariant && this.call.isSubtype(other.call);
}
return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE).isSubtype(that);
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseFunctionType(this);
}
/**
* Gets the type of instance of this function.
* @throws IllegalStateException if this function is not a constructor
* (see {@link #isConstructor()}).
*/
public ObjectType getInstanceType() {
Preconditions.checkState(hasInstanceType());
return typeOfThis;
}
/** Sets the instance type. This should only be used for special native types. */
void setInstanceType(ObjectType instanceType) {
typeOfThis = instanceType;
}
/**
* Returns whether this function type has an instance type.
*/
public boolean hasInstanceType() {
return isConstructor() || isInterface();
}
/**
* Gets the type of {@code this} in this function.
*/
public ObjectType getTypeOfThis() {
return typeOfThis.isNoObjectType() ?
registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE) : typeOfThis;
}
/**
* Gets the source node or null if this is an unknown function.
*/
public Node getSource() {
return source;
}
/**
* Sets the source node.
*/
public void setSource(Node source) {
this.source = source;
}
/** Adds a type to the list of subtypes for this type. */
private void addSubType(FunctionType subType) {
if (subTypes == null) {
subTypes = Lists.newArrayList();
}
subTypes.add(subType);
}
/**
* Returns a list of types that are subtypes of this type. This is only valid
* for constructor functions, and may be null. This allows a downward
* traversal of the subtype graph.
*/
public List<FunctionType> getSubTypes() {
return subTypes;
}
@Override
public boolean hasCachedValues() {
return prototype != null || super.hasCachedValues();
}
/**
* Gets the template type name.
*/
public String getTemplateTypeName() {
return templateTypeName;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {